Entwicklung eines Replay-Systems (Teil 68): Das richtige Bestimmen der Zeit (I)
Einführung
In dem Artikel „Entwicklung eines Replay Systems (Teil 66): Abspielen des Dienstes (VII)“ habe ich gezeigt, wie man den Mausindikator dazu bringt, uns darüber zu informieren, wie viel Zeit noch verbleibt, bevor der aktuelle Balken endet und ein neuer auf dem Chart zu erscheinen beginnt. Diese Methode funktioniert zwar sehr gut und ist recht effizient, aber sie hat ein Problem. Obwohl viele annehmen, dass das Problem bei der Methode selbst liegt, hängt es in Wirklichkeit mit der Liquidität des Handelssymbols zusammen. Wenn Sie die Erläuterungen im Artikel aufmerksam gelesen haben, ist Ihnen vielleicht aufgefallen, dass der Mausindikator die Informationen nur aktualisiert, wenn ein neues Angebot eingeht.
Dies geschieht, weil wir uns auf MetaTrader 5 verlassen, der das Ereignis OnCalculate auslöst, sodass der Indikator einen neuen Wert in Sekunden zu erhalten, um die verbleibende Zeit des aktuellen Balkens zu berechnen. Wie in dem Artikel gezeigt, wird dieser Wert über Spread eingestellt. Da wir uns jedoch auf den Wert des Dienstes im Spread verlassen, können wir die verbleibende Zeit des Balkens von Vermögenswerten mit geringer Liquidität (oder in Zeiten geringer Liquidität) nicht genau angeben. In Fällen, in denen der Vermögenswert in einen Auktionsstatus eintritt, wird das Problem noch deutlicher, da der Markt für mehrere Minuten ausgesetzt bleiben kann. Leider gibt es keine zuverlässige Lösung für das Auktionsszenario. Selbst bei Vermögenswerten mit guter Liquidität werden während einer Auktionsperiode keine Ereignisse für OnCalculate ausgelöst. Daher muss die Wiedergabe/Simulation aktualisiert werden, um sicherzustellen, dass der Nutzer in diesen beiden Szenarien richtig informiert wird. Erstens, wenn die Liquidität gering ist und nur wenige Abschlüsse pro Sekunde getätigt werden. Und zweitens, wenn der Vermögenswert in eine Auktion geht. Diese beiden Probleme müssen gelöst werden, bevor wir zur nächsten Entwicklungsstufe übergehen können.
Umsetzung der Lösung für geringe Liquidität
Das erste Szenario, bei dem die Liquidität gering ist, ist relativ einfach und leicht zu lösen, da es hauptsächlich Änderungen am Dienstleistungskodex erfordert, zumindest in allgemeiner Hinsicht. Möglicherweise sind Änderungen an anderer Stelle erforderlich. Vorerst werden wir jedoch versuchen, das Problem zu lösen, indem wir nur den Quellcode der Wiedergabe/Simulation ändern.
Um zu ermitteln, ob dies möglich ist, werden wir unseren ursprünglichen Quellcode vorübergehend beiseite legen. Wir werden einen separaten Testcode erstellen, um zu prüfen, ob der Dienst, auch wenn er inaktiv ist, den Mauszeiger hinsichtlich der verbleibenden Zeit bis zum Schließen des aktuellen Balkens aktualisieren kann.
Der Code, den wir entwickeln müssen, sollte so einfach wie möglich sein, ohne übermäßig komplex oder aufwändig zu sein. Ziel ist es, zu testen, ob es tatsächlich möglich ist, Daten vom Server an den Indikator zu senden, ohne die im Chart angezeigten Informationen zu beeinflussen. Der zu diesem Zweck zu verwendende Quellcode ist nachstehend aufgeführt:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. //+------------------------------------------------------------------+ 06. #include <Market Replay\Defines.mqh> 07. //+------------------------------------------------------------------+ 08. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 09. //+------------------------------------------------------------------+ 10. void OnStart() 11. { 12. long id; 13. int handle; 14. MqlRates Rate[1]; 15. 16. Print("Starting Test Service..."); 17. SymbolSelect(def_SymbolReplay, false); 18. CustomSymbolDelete(def_SymbolReplay); 19. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 20. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5); 21. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5); 22. Rate[0].close = 105; 23. Rate[0].open = 100; 24. Rate[0].high = 110; 25. Rate[0].low = 95; 26. Rate[0].tick_volume = 5; 27. Rate[0].spread = 1; 28. Rate[0].real_volume = 10; 29. Rate[0].time = D'10.03.2023 09:00'; 30. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 31. Rate[0].time = D'10.03.2023 09:30'; 32. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 33. SymbolSelect(def_SymbolReplay, true); 34. id = ChartOpen(def_SymbolReplay, PERIOD_M30); 35. Sleep(1000); 36. if ((handle = iCustom(NULL, 0, "\\Indicators\\Mouse Study.ex5")) != INVALID_HANDLE) 37. ChartIndicatorAdd(id, 0, handle); 38. IndicatorRelease(handle); 39. Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS)); 40. while (def_Loop) 41. { 42. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 43. Sleep(250); 44. Rate[0].spread++; 45. if (Rate[0].spread == 60) 46. { 47. Rate[0].time += 60; 48. Rate[0].spread = 0; 49. Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS)); 50. EventChartCustom(id, evSetServerTime, (long)(Rate[0].time), 0, ""); 51. } 52. } 53. ChartClose(id); 54. SymbolSelect(def_SymbolReplay, false); 55. CustomSymbolDelete(def_SymbolReplay); 56. Print("Finished Test Service..."); 57. } 58. //+------------------------------------------------------------------+
Quellcode für den Test des Dienstes
Beachten Sie, dass der Mausindikator kompiliert sein muss und sich in dem speziellen Verzeichnis befinden muss, das ich im Laufe der Erklärung zeigen werde, damit das unten beschriebene Verfahren korrekt funktioniert. Andernfalls wird der Test fehlschlagen. Verstehen wir also, was ausgeführt werden muss und wie wir dies später zu unserem Vorteil nutzen können, vorausgesetzt, der Test erweist sich als effektiv für unseren beabsichtigten Zweck.
Die ersten vier Zeilen sind Deklarationen und Eigenschaftseinstellungen, die sich auf den Typ der zu erzeugenden ausführbaren Datei beziehen. In Zeile 6 binden wir eine Header-Datei ein, um zu vermeiden, dass wir in diesem Skript zusätzliche Komponenten deklarieren müssen. Zeile 8 definiert eine Kontrollrichtlinie für die Schleife, die wir in Kürze implementieren werden. In Wahrheit ist die einzige wirklich wichtige Zeile unter diesen ersten acht Zeilen die Zeile 2. Die anderen könnten weggelassen werden, und ihr jeweiliger Code könnte an den entsprechenden Stellen eingefügt werden. Zeile 2 ist jedoch wichtig, da sie MetaTrader 5 mitteilt, dass die erzeugte ausführbare Datei ein Dienst sein soll. Andernfalls würde es als Skript interpretiert werden, was wir hier nicht testen wollen.
Ab Zeile 10 werden die Dinge interessanter. Zwischen den Zeilen 12 und 14 deklarieren wir die Variablen, die wir verwenden werden. Dann, in den Zeilen 17 bis 21, beginnen wir mit der Initialisierung der Erstellung des nutzerdefinierten Symbols. Das sind Standardschritte, damit wir vorankommen können. Zwischen den Zeilen 22 und 28 erstellen wir nun die Struktur RATE für den Balken. Wichtig ist hier vor allem die Zeile 29, in der wir den Zeitstempel festlegen, zu dem der Balken erstellt wird. In Zeile 30 geben wir die Balkendaten an, die MetaTrader 5 für die Erstellung verwenden soll. Passen Sie jetzt gut auf! In Zeile 31 wird ein zweiter Balken mit denselben Daten erstellt, diesmal jedoch mit einem Versatz von 30 Minuten. Der Grund für diesen Versatz ist, dass das Chart mit einem 30-Minuten-Zeitrahmen geöffnet wird. Wenn Sie bei der Erstellung des Balkens in Zeile 31 einen anderen Zeitwert verwenden, berechnet der Mauszeiger, wie viel Zeit bis zum Schließen des Balkens verbleibt. Achten Sie also auf dieses Detail. Ein weiterer kritischer Punkt: KEINEN Wert in Sekunden angeben. Der Wert muss in Minuten angegeben werden, niemals in Sekunden. Der nächste Schritt ist die Erstellung des Balkens in Zeile 32.
In Zeile 34 öffnen wir das Chart. Wie im vorherigen Artikel dieser Serie beschrieben, muss der Dienst eine Weile warten, bevor er dem Chart etwas hinzufügen kann, sei es ein Objekt oder ein Indikator. Dadurch hat die MetaTrader 5-Plattform genügend Zeit, um das Chart ordnungsgemäß zu erstellen und auf dem Bildschirm darzustellen. Diese Pause tritt in Zeile 35 auf, wo wir eine Sekunde warten, bevor wir die Ausführung fortsetzen.
Auch hier ist in Zeile 36 Vorsicht geboten. Hier versuchen wir, einen Handle zu erstellen, um den Mausindikator auf dem Chart zu platzieren. Die Funktion iCustom gibt den Handle zurück, der zum Hinzufügen des Indikators erforderlich ist (falls der Indikator gefunden wird). Wenn es nicht gefunden wird, gibt die Funktion ein ungültiges Handle zurück, und wir fahren nicht mit der Verwendung fort. Daher muss sich der Mauszeiger an der angegebenen Stelle befinden. Andernfalls wird der Test fehlschlagen. Wenn der Indikator erfolgreich gefunden wird, fügen wir ihn dem Chart hinzu. Dies geschieht in Zeile 37. Da der von iCustom zurückgegebene Handle danach nicht mehr benötigt wird, geben wir ihn in Zeile 38 frei.
Jetzt kommt der eigentliche Test. Das ist der Teil, der uns wirklich interessiert, wo wir feststellen, ob der Ansatz funktioniert. Wenn das der Fall ist, haben wir eine Möglichkeit, das Problem mit Szenarien mit geringer Liquidität zu lösen. Wenn das nicht der Fall ist, müssen wir nach alternativen Lösungen suchen. Wir müssen also verstehen, wie dieser Test durchgeführt wird. Sie findet in einer Schleife statt, die in Zeile 40 beginnt und in Zeile 52 endet. Der restliche Code (Zeilen 53 bis 56) schließt den Test einfach ab und ist für diesen Artikel nicht besonders relevant. Konzentrieren wir uns also auf den Test selbst.
Die Funktionsweise des Tests verstehen
Sie beginnt in Zeile 40, wo wir die Schleife betreten. Wenn das Chart geöffnet ist und der Nutzer den Dienst nicht gestoppt hat, läuft die Schleife unendlich lange. In der Schleife angekommen, erreichen wir Zeile 42. Mit dieser Zeile werden die Daten des Balkens zwangsweise aktualisiert. Es ist jedoch wichtig zu wissen, dass keine neuen Informationen an die Bar weitergegeben werden, sondern nur das, was wir zu Testzwecken ändern. Wir machen Folgendes. In Zeile 43 fügen wir eine kurze Verzögerung ein, um zu verhindern, dass die Aktualisierungen zu schnell erfolgen. Beachten Sie den Verzögerungswert: Wir warten nicht eine ganze Sekunde. Es ist nur eine Viertelsekunde, und das ist wichtig, wenn Sie später die Ergebnisse im MetaTrader 5 betrachten.
In Zeile 44 wird dann der Wert der Spanne erhöht. Dies vermittelt dem Mauszeiger den Eindruck, dass eine Sekunde vergangen ist, obwohl die tatsächliche Verzögerung kürzer ist. Der Zweck dieser Funktion hängt direkt damit zusammen, wie die Plattform OnCalculate-Ereignisse auslöst. Bis zu diesem Punkt ist das erwartete Verhalten wie folgt: Sobald der Chart geöffnet und der Mausindikator angezeigt wird, sollte MetaTrader 5 Ereignisse für OnCalculate generieren, sodass die verbleibende Zeit für den aktuellen Balken entsprechend dekrementiert werden kann. Sie können dies selbst überprüfen. Ich habe es jedoch bereits getestet und bestätigt, dass es wie erwartet funktioniert. Aus Neugierde beschloss ich zu sehen, was passieren würde, wenn der Spread-Wert 60 übersteigt. Warum ist dieser Wert so wichtig? Denn sobald der Wert 60 erreicht, sollte ein neuer Ein-Minuten-Balken erscheinen, auch wenn Sie einen monatlichen Zeitrahmen verwenden. Der tatsächliche Zeitrahmen des Charts ist hier irrelevant. Für unsere Zwecke reicht es aus, wenn der MetaTrader 5 darüber informiert wird, dass sich ein neuer einminütiger Balken gebildet hat.
Deshalb müssen wir sicherstellen, dass unser Code dies richtig handhabt. MetaTrader 5 kann diese Situation nicht selbständig beheben. In Zeile 45 wird geprüft, ob die Iterationszahl 60 erreicht hat. Wenn dies der Fall ist, werden in Zeile 47 60 Sekunden (eine Minute) zur ursprünglichen Balkenzeit hinzugefügt. Auf diese Weise erkennt der MetaTrader 5 bei der nächsten Ausführung der Funktion in Zeile 42 einen neuen einminütigen Balken. Aber das ist noch nicht genug. In Zeile 48 setzen wir den Iterationszähler zurück, sodass ein neuer Zyklus beginnen kann. Zur Überprüfung wird in Zeile 49 der an den MetaTrader 5 gesendete Wert ausgedruckt.
Ein kritischer Punkt: Bisher wurde nur der MetaTrader 5 über die Existenz eines neuen Balkens informiert. Wie wir jedoch in dem Artikel gesehen haben, in dem wir das System zur Anzeige der verbleibenden Zeit in der Mausanzeige implementiert haben, können wir uns nicht auf den Zeitwert verlassen, der von der Funktion OnCalculate bereitgestellt wird. Zumindest noch nicht. Ich bin gerade dabei, eine Lösung für diese Einschränkung zu finden, die die Zeile 50 überflüssig machen würde. Bis ein solcher Workaround implementiert ist, muss MetaTrader 5 in Zeile 50 gezwungen werden, ein eigenes Ereignis zu erzeugen. Damit wird dem Mauszeiger mitgeteilt, dass sich ein neuer Balken gebildet hat. Auf diese Weise kann der Indikator den aktuellen Zeitpunkt korrekt identifizieren und uns genau mitteilen, wie viel Zeit noch verbleibt, bevor der aktuelle Balken schließt und ein neuer beginnt.
Wenn Sie es vorziehen, diesen Dienst nicht in MetaTrader 5 zu kompilieren und zu testen, ist das kein Problem. Im folgenden Video zeige ich, wie sich der Dienst verhält und ob dieser Testcode für die Verwendung der Wiedergabe/Simulation brauchbar ist. Nach meiner Einschätzung hat der Test außerordentlich gut funktioniert. Er bewies sowohl die Fähigkeit als auch die Durchführbarkeit der Integration des getesteten Mechanismus in die Wiedergabe/Simulations-Anwendung. Dies würde es uns ermöglichen, auch in Zeiten geringer Liquidität oder bei Symbolen mit geringer Handelsaktivität eine genaue Zeitverfolgung aufrechtzuerhalten und sicherzustellen, dass die Nutzer darüber informiert sind, wie viel Zeit noch verbleibt, bis ein neuer Balken im Chart erscheint.
Um den getesteten Mechanismus in die Wiedergabe/Simulations-Anwendung zu integrieren, müssen wir einen ganz bestimmten Abschnitt des Codes leicht ändern. Dieser Abschnitt befindet sich in der Header-Datei C_Replay.mqh. Doch bevor wir uns an die Umsetzung dieser Änderungen machen, müssen wir zunächst ein anderes Konzept verstehen. Um die Erklärung besser zu strukturieren, lassen Sie uns zu einem neuen Thema übergehen.
Zeit, Zeit, Zeit - wie können wir sie verstehen?
Auch wenn manche Dinge zunächst verwirrend erscheinen mögen, werde ich Ihnen etwas zeigen, das zwar sehr komplex erscheint, aber durchaus möglich und logisch ist. Dabei geht es vor allem um die Struktur der Daten.
Wenn Sie sich die Funktion OnCalculate ansehen, können Sie sehen, dass der Wert des Spread als int angegeben wird. Bei 64-Bit-Prozessoren bedeutet dies, dass wir mit 32 Bit Daten arbeiten. Wenn Sie den Datentyp datetime überprüfen, werden Sie feststellen, dass er 64 Bit verwendet. Ja. Aber wie helfen uns diese Details hier weiter? Geduld, lieber Leser. Geduld. Wir müssen jetzt ein bisschen rechnen, aber nichts Kompliziertes, nur ein paar grundlegende Berechnungen.
Was wir brauchen, ist, dass der Dienst MetaTrader 5 dazu zwingt, eine Art Ereignis zu erzeugen, das den Timer im Mausindikator aktualisiert. Das ist eine Selbstverständlichkeit, und wir handhaben das bereits auf zwei verschiedene Arten. Zum einen durch den Aufruf der Funktion OnCalculate und zum anderen durch ein nutzerdefiniertes Ereignis, das wir regelmäßig auslösen. Um zu sehen, wo dies im Dienst gehandhabt wird, sehen Sie sich den Code der Header-Datei C_Replay.mqh an:
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. //+------------------------------------------------------------------+
Der Code aus der Datei C_Replay.mqh
Dies wurde bereits in dem Artikel „Entwicklung eines Replay Systems (Teil 66): Abspielen des Dienstes (VII)“ erläutert, aber hier werden wir die Erklärung verstärken. Jedes Mal, wenn diese Prozedur aufgerufen wird und ein neuer Tick zu dem Balken hinzugefügt wird, der gerade auf dem Chart dargestellt wird, haben wir Zeile 96, in der der Wert a (in Sekunden) zur Spanne hinzugefügt wird. So können wir die verbleibende Zeit für den aktuellen Balken berechnen. Na gut. Außerdem gibt es in Zeile 94 eine Bedingung, die wahr wird, sobald ein neuer Ein-Minuten-Balken erscheint, was MetaTrader 5 dazu zwingt, ein nutzerdefiniertes Ereignis auszulösen. Dieses Ereignis ermöglicht es dem Mauszeiger, sein internes Timing neu zu justieren und korrekt zu erkennen, wann der neue Balken erzeugt wurde. Der Grund für diese Vorgehensweise ist, dass ein zusätzlicher Aufruf vermieden werden soll, der im Mauszeiger selbst sehr intensiv ausgeführt würde. Ich habe dies in dem bereits erwähnten Artikel ausführlicher erläutert.
Wir verschwenden jedoch Zeit und Platz, wenn wir es auf diese Weise handhaben. Wir können etwas effizienter sein und mehr Informationen mit weniger Ressourcen als bei der derzeitigen Methode übermitteln. Jetzt kommt der rechnerische Teil. Wir müssen nur den Ablauf der Zeit in Ein-Sekunden-Intervallen verfolgen; wir brauchen keine schnelleren Zeiten. Damit haben wir einen klaren Ausgangspunkt: Eine Minute hat 60 Sekunden, eine Stunde 60 Minuten und ein Tag 24 Stunden. Das sind insgesamt 86.400 Sekunden pro Tag (ungefähr, denn ein Tag hat nicht genau 24 Stunden, aber für unsere Zwecke nehmen wir das an). Na gut. Eine 32-Bit-Ganzzahl ohne Vorzeichen kann einen maximalen Wert von 4.294.967.295 haben. Unter der Annahme, dass wir einen Wert ohne Vorzeichen haben (was der Fall ist), können wir in einem einzigen 32-Bit-Feld Sekunden im Wert von etwa 49 Tagen speichern. Das ist ein wichtiger Punkt. Innerhalb eines einzigen Streuungswertes, der 32 Bit umfasst, können wir etwa 49 Tage darstellen. In der Praxis ist es jedoch unwahrscheinlich, dass unsere Wiedergabe/Simulations-Anwendung für Studien verwendet wird, die sich über mehr als einen oder zwei aufeinanderfolgende Tage erstrecken. Daher können wir die erforderlichen Daten vollständig über das Spread-Feld synchronisieren und müssen MetaTrader 5 nicht zwingen, ein nutzerdefiniertes Ereignis auszugeben, nur um den Mausindikator darüber zu informieren, dass sich ein neuer Balken gebildet hat.
Jetzt wird es interessant, nicht wahr? Um die Sache noch effizienter zu machen, werde ich noch einen Schritt weiter gehen und ein ausgeklügeltes Modell implementieren, um die 49-Tage-Grenze ganz zu umgehen. Realistischerweise bezweifle ich, dass jemand jemals diese Obergrenze erreichen wird, aber ich werde trotzdem etwas anderes einführen. Wir werden eine Kontrollstrategie auf Bitebene anwenden, die uns mehr Flexibilität bietet. Dieser Ansatz erfordert einige Anpassungen des Mauszeigers. Aber nichts übermäßig Kompliziertes, wenn man es mit den Vorteilen vergleicht, die es bringt.
Doch werden wir damit wirklich die gewünschte Lösung finden? Um das herauszufinden, müssen wir uns ein weiteres Codefragment ansehen, ebenfalls aus der Header-Datei C_Replay.mqh. Es ist unten aufgeführt:
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. //+------------------------------------------------------------------+
Der Code aus der Datei C_Replay.mqh
Nun gut, unser Problem tritt auf, wenn der Vermögenswert eine geringe Liquidität aufweist. Das heißt, dass die Ticks nicht jede Sekunde in einem kontinuierlichen Fluss erzeugt werden. Zu bestimmten Zeiten kann die Liquidität ausreichen, um jede Sekunde einen Tick zu erzeugen, was für unsere Zwecke ideal ist. Es gibt jedoch auch Momente, in denen dieses Liquiditätsniveau nicht erreicht wird. Dennoch können Sie feststellen, dass wir in Zeile 225 das zuvor besprochene Codefragment aufrufen. Und selbst wenn die Zeit zwischen den Ticks mehr als eine Sekunde beträgt, wird der Aufruf in Zeile 225 tatsächlich erfolgen. Die Nutzung der Streuung zur Übermittlung von Zeitinformationen ist also in der Tat eine praktikable Lösung. Die Umsetzung aller erforderlichen Mechanismen kann jedoch recht komplex sein. Aus diesem Grund ist es nicht möglich, in diesem Artikel alle Einzelheiten zu behandeln. Aber lassen Sie uns dennoch mit den Grundlagen beginnen.
Beginn einer neuen Phase
Der erste Schritt besteht darin, die Header-Datei Defines.mqh zu ändern. Die Änderungen sind unten zu sehen:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. //+------------------------------------------------------------------+ 17. union uCast_Double 18. { 19. double dValue; 20. long _long; // 1 Information 21. datetime _datetime; // 1 Information 22. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 23. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 24. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 25. }; 26. //+------------------------------------------------------------------+ 27. enum EnumEvents { 28. evHideMouse, //Hide mouse price line 29. evShowMouse, //Show mouse price line 30. evHideBarTime, //Hide bar time 31. evShowBarTime, //Show bar time 32. evHideDailyVar, //Hide daily variation 33. evShowDailyVar, //Show daily variation 34. evHidePriceVar, //Hide instantaneous variation 35. evShowPriceVar, //Show instantaneous variation 36. evSetServerTime, //Replay/simulation system timer 37. evCtrlReplayInit, //Initialize replay control 38. }; 39. //+------------------------------------------------------------------+
Der Quellcode der Datei Defines.mqh
Beachten Sie, dass Zeile 36 durchgestrichen ist, was bedeutet, dass sie in der endgültigen Fassung der Datei nicht mehr existiert. In ähnlicher Weise haben wir jetzt eine neue Definition in Zeile 15, die als Maske dient und uns ein bequemeres Arbeiten ermöglicht. Die bloße Entfernung von Zeile 36, die für die Aktualisierung des Zeitgebers verantwortlich war, deutet bereits darauf hin, dass einige Anpassungen erforderlich sein werden, um den neuen Code mit der aktualisierten Richtlinie für die Behandlung von Nachrichten kompatibel zu machen.
So wie wir Änderungen an der Datei Defines.mqh vorgenommen haben, gibt es weitere Aktualisierungen in einer anderen Header-Datei, diesmal Macros.mqh. Die aktualisierte Version dieser Datei ist unten abgebildet:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define macroRemoveSec(A) (A - (A % 60)) 05. #define macroGetDate(A) (A - (A % 86400)) 06. #define macroGetSec(A) (A - (A - (A % 60))) 07. #define macroGetTime(A) (A % 86400) 08. //+------------------------------------------------------------------+ 09. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16))) 10. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0) 11. //+------------------------------------------------------------------+
Der Quellcode der Datei Macros.mqh
Hier haben wir nur Zeile 7 hinzugefügt, die ein Makro einführt, mit dem wir den Zeitwert aus einer Variablen vom Typ datetime extrahieren können. Da es sich um eine einfache Ergänzung handelt, können wir mit der Überprüfung einer anderen Header-Datei fortfahren. Dieses Mal ist die Header-Datei C_Stuty.mqh, die unten in voller Länge angezeigt werden kann. Diese Datei ist Teil des Mauszeigers.
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 ? (datetime) m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent()); 125. dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent()); 126. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 127. if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 128. break; 129. case eAuction : 130. m_Info.szInfo = "Auction"; 131. break; 132. default : 133. m_Info.szInfo = "ERROR"; 134. } 135. Draw(); 136. } 137. //+------------------------------------------------------------------+ 138. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 139. { 140. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 141. switch (id) 142. { 143. case CHARTEVENT_CUSTOM + evHideBarTime: 144. RemoveObjInfo(evHideBarTime); 145. break; 146. case CHARTEVENT_CUSTOM + evShowBarTime: 147. CreateObjInfo(evShowBarTime); 148. break; 149. case CHARTEVENT_CUSTOM + evHideDailyVar: 150. RemoveObjInfo(evHideDailyVar); 151. break; 152. case CHARTEVENT_CUSTOM + evShowDailyVar: 153. CreateObjInfo(evShowDailyVar); 154. break; 155. case CHARTEVENT_CUSTOM + evHidePriceVar: 156. RemoveObjInfo(evHidePriceVar); 157. break; 158. case CHARTEVENT_CUSTOM + evShowPriceVar: 159. CreateObjInfo(evShowPriceVar); 160. break; 161. case (CHARTEVENT_CUSTOM + evSetServerTime): 162. m_Info.TimeDevice = (datetime)lparam; 163. break; 164. case CHARTEVENT_MOUSE_MOVE: 165. Draw(); 166. break; 167. } 168. ChartRedraw(GetInfoTerminal().ID); 169. } 170. //+------------------------------------------------------------------+ 171. }; 172. //+------------------------------------------------------------------+ 173. #undef def_ExpansionPrefix 174. #undef def_MousePrefixName 175. //+------------------------------------------------------------------+
Der Quellcode der Datei C_Study.mqh
Alle durchgestrichenen Zeilen in dieser Datei sollten aus der Originaldatei entfernt werden. Während diese Zeilen früher Teil des alten Codes waren, mit dem das nutzerdefinierte Ereignis den Timer des Indikators synchron hielt, ist dies nun nicht mehr notwendig, da wir jetzt eine andere Methode für diese Aufgabe implementieren. Es ist jedoch wichtig, dass Sie ein bestimmtes Detail in dieser Kopfdatei beachten. Sehen Sie sich Zeile 124 an und vergleichen Sie sie mit Zeile 125. Es wird zwar angegeben, dass die gesamte Zeile ersetzt wurde, aber in Wirklichkeit wurde die Variable m_Info.TimeDevice entfernt. Der Grund dafür ist, dass er nicht mehr benötigt wird, da er früher dazu diente, den Timer zu synchronisieren. Damit kommen wir sozusagen zu einem neuen Problem, das im Code des Mauszeigers selbst vorhanden ist. Der vollständige Code des Indikators ist unten zu finden:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.68" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. #property indicator_applied_price PRICE_CLOSE 13. //+------------------------------------------------------------------+ 14. double GL_PriceClose; 15. datetime GL_TimeAdjust; 16. //+------------------------------------------------------------------+ 17. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 18. //+------------------------------------------------------------------+ 19. C_Study *Study = NULL; 20. //+------------------------------------------------------------------+ 21. input color user02 = clrBlack; //Price Line 22. input color user03 = clrPaleGreen; //Positive Study 23. input color user04 = clrLightCoral; //Negative Study 24. //+------------------------------------------------------------------+ 25. C_Study::eStatusMarket m_Status; 26. int m_posBuff = 0; 27. double m_Buff[]; 28. //+------------------------------------------------------------------+ 29. int OnInit() 30. { 31. ResetLastError(); 32. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 33. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 34. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 35. { 36. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 37. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 38. m_Status = C_Study::eCloseMarket; 39. }else 40. m_Status = C_Study::eInReplay; 41. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 42. ArrayInitialize(m_Buff, EMPTY_VALUE); 43. 44. return INIT_SUCCEEDED; 45. } 46. //+------------------------------------------------------------------+ 47. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 48. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 49. const long& volume[], const int& spread[]) 50. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[]) 51. { 52. GL_PriceClose = close[rates_total - 1]; 53. GL_PriceClose = price[rates_total - 1]; 54. GL_TimeAdjust = (spread[rates_total - 1] < 60 ? spread[rates_total - 1] : 0); 55. if (_Symbol == def_SymbolReplay) 56. GL_TimeAdjust = iSpread(NULL, PERIOD_M1, 0) & (~def_MaskTimeService); 57. m_posBuff = rates_total; 58. (*Study).Update(m_Status); 59. 60. return rates_total; 61. } 62. //+------------------------------------------------------------------+ 63. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 64. { 65. (*Study).DispatchMessage(id, lparam, dparam, sparam); 66. (*Study).SetBuffer(m_posBuff, m_Buff); 67. 68. ChartRedraw((*Study).GetInfoTerminal().ID); 69. } 70. //+------------------------------------------------------------------+ 71. void OnBookEvent(const string &symbol) 72. { 73. MqlBookInfo book[]; 74. C_Study::eStatusMarket loc = m_Status; 75. 76. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 77. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 78. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 79. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 80. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 81. if (loc != m_Status) (*Study).Update(m_Status); 82. } 83. //+------------------------------------------------------------------+ 84. void OnDeinit(const int reason) 85. { 86. if (reason != REASON_INITFAILED) 87. { 88. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 89. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 90. } 91. delete Study; 92. } 93. //+------------------------------------------------------------------+
Der Quellcode des Mauszeigers
Dieser Code verhält sich manchmal so, dass er mich bei Tests ernsthaft frustriert. Und es gibt noch andere Elemente, die ich getestet habe, auf die ich aber später eingehen werde, die unglaublich ärgerlich sind, vor allem weil sie keinen Sinn ergeben. Diese Dinge sollten funktionieren, aber aus irgendeinem bizarren und unerklärlichen Grund tun sie es einfach nicht. Ich werde diese Themen für einen anderen Artikel aufheben, in dem ich sie in aller Ruhe erläutern kann. Zunächst möchte ich Sie, liebe Leserinnen und Leser, auf einige wichtige Änderungen aufmerksam machen, die vorgenommen werden mussten.
Fangen wir am Ende an, damit wir den Anfang verstehen können. Schauen Sie sich die Zeilen 47 bis 49 an, sie wurden durchgestrichen und durch Zeile 50 ersetzt. Mit anderen Worten, die Ereignisbehandlung von OnCalculate empfängt nicht mehr alle diese Parameter. Sie fragen sich vielleicht, warum ich diese Änderung vorgenommen habe. Vor allem, nachdem ich zuvor erwähnt hatte, dass so etwas keine gute Idee ist. Ich werde den Grund für diese taktische Änderung erklären, wenn ich über den Dienstleistungskodex berichte, aber nicht hier. Beachten Sie nun, dass die Zeile 52 durch die Zeile 53 und die Zeile 54 durch die Zeilen 55 und 56 ersetzt wurde. Darüber hinaus gibt es eine weitere Änderung: die Einführung der Zeile 12. Doch bevor wir zu Zeile 12 kommen, sollten wir uns genauer ansehen, was in den Zeilen 55 und 56 passiert.
Wenn der Dienst den Kurs aktualisiert (wir werden später sehen, wie genau das geschieht), löst MetaTrader 5 ein Ereignis für OnCalculate aus, das dann von der Funktion OnCalculate verarbeitet wird. Na gut. Wenn der Zeitrahmen des Charts jedoch nicht eine Minute beträgt, wird der Wert rates_total NICHT aktualisiert, bis ein neuer Balken gebildet wird. Da wir den Spread-Wert nur während der Wiedergabe/Simulation verwenden, prüfen wir, ob das Symbol mit dem erwarteten übereinstimmt. Wenn dies der Fall ist, holen wir uns den Spread-Wert mit der Funktion iSpread. Warum wir dies tun, anstatt nur den OnCalculate-Parameter zu verwenden, sowie die Berechnungen in Zeile 56, werden wir im nächsten Artikel erläutern.
Da wir nun die Funktion iSpread verwenden, um den richtigen Wert abzurufen, müssen wir OnCalculate nicht mehr mit allen früheren Parametern aufrufen. Daher wird in Zeile 53 der Preiswert korrekt erfasst. Die Frage ist nun folgende: Welcher Preis? Es gibt mehr als einen, den wir gebrauchen könnten. Und hier kommt die Zeile 12 ins Spiel. Er legt als Eigenschaft des Indikators fest, welchen Preis wir tatsächlich verwenden werden. In diesem Fall haben wir uns dafür entschieden, den Schlusskurs jedes Balkens zu verwenden. Dadurch bleibt der Mauszeiger mit den zuvor im Chart angezeigten Werten kompatibel.
Abschließende Überlegungen
In diesem Artikel habe ich einige ungelöste Fragen behandelt, auf die ich in den nächsten Beiträgen näher eingehen werde. Einige Dinge, die hier erwähnt werden, ergeben vielleicht keinen Sinn, wenn Sie nur diesen Artikel lesen. Zum Beispiel, warum wir iSpread verwenden, anstatt einfach den durch OnCalculate übergebenen Spread-Wert zu lesen, was MetaTrader 5 bereits bietet. Es gibt noch ein weiteres Thema, das wir noch nicht besprochen haben: was passiert, wenn der Vermögenswert in den Auktionsmodus übergeht. Dies ist ein besonders merkwürdiger Fall, wenn man mit einem nutzerdefinierten Symbol arbeitet. Nicht, weil der Mauszeiger nicht anzeigen kann, wenn sich ein Vermögenswert im Auktionsmodus befindet. Das Problem liegt woanders.
Die Funktionalität ist bereits implementiert und funktioniert. Sie können dies selbst überprüfen, indem Sie sich die Funktion OnBookEvent im Code des Mauszeigers ansehen. Sie werden sehen, dass wir nur den Wert BOOK_TYPE_BUY_MARKET oder BOOK_TYPE_SELL_MARKET melden müssen, damit der Mauszeiger anzeigt, dass sich die Anlage in einer Auktion befindet. Aber ich werde in einem anderen Artikel genau erklären, wie man das macht. Das ist alles für den Moment. Wir sehen uns in der nächsten Ausgabe.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12309
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Von der Grundstufe bis zur Mittelstufe: Arrays und Zeichenketten (III)
DoEasy. Dienstfunktionen (Teil 3): Das Muster der „Outside Bar“
Neuronale Netze im Handel: Verallgemeinerte 3D-Segmentierung von referenzierten Ausdrücken
Entwicklung eines Replay-Systems (Teil 67): Verfeinerung des Kontrollindikators
- 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.