Vom Anfänger zum Fortgeschrittenen: Objekte (II)
Einleitung
Im vorherigen Artikel „Von den Grundlagen bis zur Mittelstufe: Objekte (I)“ erhielten wir einen ersten Einblick in die in MQL5 verfügbaren grafischen Objekte und zeigten auch, wie diese Objekte auf einem Chart platziert und auf relativ einfache, wenn auch im Allgemeinen nicht sehr effiziente Weise bearbeitet werden können. Das liegt daran, dass wir uns noch nicht mit der Entwicklung von Anwendungen beschäftigen wollen; unser Ziel ist es, den Code so einfach und übersichtlich wie möglich zu halten.
Ich muss zugeben, dass der vorherige Artikel ziemlich interessant war. Er ist jedoch bei weitem nicht so komplex wie das, was wir heute machen werden. Der Punkt ist: Sobald wir anfangen, Objekte mithilfe von Code zu manipulieren und zu steuern, werden Dinge, die früher unmöglich schienen, immer alltäglicher.
Da wir uns jedoch noch am Anfang dieser Reise befinden, gibt es einige Punkte, die wir klären müssen, bevor wir unserer Fantasie freien Lauf lassen und MetaTrader 5 aus Sicht eines Programmierers zu etwas wirklich Interessantem machen können. Aus Sicht eines Nutzers mag das, was wir vorhaben, sinnlos erscheinen. Beginnen wir also wie üblich mit einem neuen Thema.
Erste Manipulationen
Was wir im vorherigen Artikel beschrieben haben, sollte noch nicht unbedingt als erste tatsächliche Manipulation von Objekten angesehen werden. Das liegt ganz einfach daran, dass wir sie auf dem Chart platziert haben, und das war’s auch schon. Um eine andere Aktion auszuführen, müssten wir ein Dialogfeld öffnen, in dem wir auf bestimmte Objekteigenschaften zugreifen und diese verwalten könnten. Beim Programmieren können wir jedoch noch viel weiter gehen, und theoretisch sind unseren Möglichkeiten keine Grenzen gesetzt – außer unserer Vorstellungskraft. Da MQL5 für die Entwicklung von Anwendungen zur Verwendung in MetaTrader 5 konzipiert wurde und die MetaTrader-5-Plattform zur Darstellung von Kurs-Charts vorgesehen ist, ist der Umfang der direkten Nutzung von MQL5 tatsächlich begrenzt.
Aber glauben Sie mir, liebe Leser, diese Grenze liegt tatsächlich weit über dem, was sich die meisten Menschen vorstellen. Und sobald diese Grenze erreicht ist, können wir sie mithilfe der Programmiersprachen C und C++ erweitern. Das ist jedoch bereits eine andere Ebene, die in dieser Artikelserie nicht behandelt wird, da wir nur bis zu der Grenze gelangen werden, die meiner Meinung nach die Mittelstufe vom fortgeschrittenen Niveau trennt. Dieses fortgeschrittene Niveau beginnt dort, wo wir Sprachen wie C und C++ einsetzen müssen, um die Möglichkeiten von MQL5 zu erweitern.
Um auf unseren Hauptpunkt zurückzukommen: Zur Bearbeitung von Objekten müssen wir Eingabe- und Benutzerinteraktionsmechanismen verwenden. Diese Mechanismen basieren auf der Tastatur und/oder der Maus, wodurch Benutzereingaben auf einfache Weise verarbeitet werden können. Wie Sie vielleicht bereits vermutet haben, bedeutet die Nutzung solcher Mechanismen jedoch, dass Ereignisse abgefangen werden müssen, da das Betriebssystem selbst – sei es Windows, Linux oder ein anderes – im Wesentlichen Ereignisse nutzt, um zwischen dem Benutzer und einer beliebigen Anwendung zu kommunizieren. Dies wurde bereits im Artikel „Von den Grundlagen bis zur Mittelstufe: Ereignisse (I)“ gezeigt. Dort haben wir diese Punkte jedoch nur oberflächlich behandelt. Hier werden wir etwas näher auf dieses Thema eingehen.
Lassen Sie uns zunächst ein kleines Code-Beispiel erstellen, das jeder auch ohne ausführliche Erklärung verstehen kann. Das erste Codefragment ist unten dargestellt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameObj "Demo" 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. ObjectCreate(0, def_NameObj, OBJ_HLINE, 0, 0, 0); 09. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrPurple); 10. 11. return INIT_SUCCEEDED; 12. }; 13. //+------------------------------------------------------------------+ 14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 15. { 16. static double p = 0; 17. 18. ObjectMove(0, def_NameObj, 0, 0, p); 19. 20. if (prev_calculated > 0) 21. p = price[prev_calculated - 1]; 22. 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnDeinit(const int reason) 27. { 28. ObjectDelete(0, def_NameObj); 29. ChartRedraw(); 30. }; 31. //+------------------------------------------------------------------+
Code 01
Wenn Sie sich also mit dem befasst haben, was wir in diesen Artikeln gezeigt haben, werden Sie diesen Code problemlos verstehen, denn er ist tatsächlich recht interessant und sehr einfach. Interessant ist er aufgrund dessen, was er bewirken kann. Vielleicht betrachten Sie ihn gerade und denken: „Nun, ich verstehe nicht, warum Sie diesen Code interessant finden. Schlimmer noch: Ich verstehe nicht, warum Sie solche Dinge in der in Zeile 14 gezeigten OnCalculate-Funktion tun. Könnten Sie erklären, was hier vor sich geht?“ Nun, liebe Leser, wenn Sie Zweifel daran haben, wie dieser Code funktioniert, liegt das daran, dass Sie das, was in diesen Artikeln gezeigt wurde, nicht in die Praxis umgesetzt haben.
Wenn dieser Code ausgeführt wird, entspricht das Ergebnis in etwa dem, was in der folgenden Animation zu sehen ist.

Animation 01
In diesem Fall würde ein einzelnes Bild nicht ausreichen, denn beim Betrachten der Animation sieht man, dass die violette Linie der Kurslinie immer einen Schritt hinterherhinkt. Manchmal berührt diese violette Linie die aktuelle Kurslinie. Die interessanteste Frage ist jedoch: Warum passiert das? Denn in den meisten Fällen markiert die violette Linie immer den vorherigen Kurs. Manchmal entspricht dieser Kurs jedoch dem aktuellen Kurs. Es sieht so aus, als hätten wir ein Problem mit dem in Code 01 gezeigten Indikator.
Nun, lieber Leser, mit dem Code ist alles in Ordnung, und mit der Plattform ist auch alles in Ordnung. Tatsächlich erfüllt die violette Linie genau die Funktion, für die sie vorgesehen ist: Sie zeigt an, wo die letzte Kursnotierung lag. Wenn MetaTrader jedoch nach dem Empfang einer neuen Kursnotierung ein neues Calculate-Ereignis auslöst, ohne dass sich der Kurs der neuen Kursnotierung ändert, fällt die violette Linie mit der aktuellen Kurslinie zusammen. Genau das sehen wir in Animation 01. Der Grund dafür findet sich in Zeile 18. Wenn wir also den letzten Kurs und den Kurs der neuen Kursnotierung betrachten, kann es eine Differenz zwischen diesen Werten geben. In diesem Fall hinkt die violette Linie stets hinter der Kurslinie her. Wenn der Kurs jedoch mit dem vorherigen übereinstimmt, fällt die violette Linie mit der aktuellen Kurslinie zusammen.
Ziemlich nützlich, oder? Viele Trader und Programmierer teilen die soeben vorgestellte Sichtweise nicht und glauben, dass ein Calculate-Ereignis immer dann ausgelöst wird, wenn sich der Kurs geändert hat. Sie haben jedoch gerade gesehen, dass dies nicht immer der Fall ist. Daher ist es sehr wichtig, die Codeoptimierung gut zu verstehen, um keinen erheblichen Leistungsabfall der MetaTrader-5-Anwendung zu verursachen.
Nachdem wir uns also mit etwas Einfachem und scheinbar Trivialem beschäftigt haben, wollen wir nun mit einem Ereignis experimentieren. In diesem Fall beginnen wir mit dem einfachsten Beispiel: der Verarbeitung von Tastaturereignissen. Ich bezeichne Tastaturereignisse als einfacher, weil wir sie nicht aktivieren oder deaktivieren müssen. Jedes Mal, wenn eine Taste gedrückt wird, löst MetaTrader 5 automatisch ein Ereignis aus. Wenn eine unserer Anwendungen auf dem Chart über einen ChartEvent-Ereignisbehandler verfügt, können wir den Tastendruck entsprechend verarbeiten und darauf reagieren. Um dieses erste System zu veranschaulichen, werden wir neuen Code erstellen, der auf Tastaturereignisse reagiert; er ist leicht verständlich und hat für Anfänger einen recht interessanten Zweck. Dieser Code ist unten aufgeführt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. switch(id) 17. { 18. case CHARTEVENT_KEYDOWN: 19. Print(lparam); 20. break; 21. } 22. }; 23. //+------------------------------------------------------------------+
Code 02
Haben Sie sich schon einmal gefragt, wie ein Programmierer feststellt, welche Taste gedrückt wurde? Woher weiß der Programmierer, ob eine Pfeiltaste oder eine Taste wie „Home“ oder „End“ gedrückt wurde? Wenn man an alphanumerische Tasten denkt, mag das wie Zauberei wirken. Aber was ist mit den anderen Tasten? Nun, eigentlich weiß der Programmierer das nicht direkt – aber er weiß genau, wie er es herausfinden kann. Hierfür wird Code verwendet, der dem oben gezeigten ähnelt. Wenn Sie diesen Code ausführen, können Sie herausfinden, welcher numerische Code beim Drücken einer bestimmten Taste generiert wird.
In Wirklichkeit unterscheidet sich der tatsächlich generierte Tastencode beim Drücken einer Taste von dem hier angezeigten Wert. Da das Betriebssystem den Wert der gedrückten Taste sozusagen „übersetzt“, teilt uns MetaTrader 5 jedoch beim Ausführen einer Anwendung wie der in Code 02 gezeigten mit, welcher Wert verwendet werden soll. Die folgende Animation zeigt das Ergebnis.

Animation 02
Bitte beachten Sie, dass dies intuitiv ist. Wenn eine Taste gedrückt wird, wird der Wert im Terminal angezeigt. Anhand dieses Werts lässt sich also feststellen, welche Taste gedrückt wurde. Denken Sie daran, dass die MQL5-Standardbibliothek eine Funktion enthält, mit der Sie überprüfen können, ob bestimmte Sondertasten gedrückt wurden. Diese Funktion stellt jedoch eine Abstraktion dessen dar, was Sie gerade gelernt haben. Ziemlich nützlich, oder?
Na gut, aber wie können wir das nutzen? Es mag etwas sinnlos erscheinen, sich damit zu beschäftigen. Schauen wir uns also an, wie wir die mit Code 02 gewonnenen Informationen nutzen können. Dazu werden wir Code 01 so ändern, dass er nun etwas anderes tut. Diese Änderung ist unten zu sehen.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameObj "Demo" 05. #define def_KEY_UP 38 06. #define def_KEY_DOWN 40 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. ObjectCreate(0, def_NameObj, OBJ_HLINE, 0, 0, 0); 11. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrPurple); 12. 13. return INIT_SUCCEEDED; 14. }; 15. //+------------------------------------------------------------------+ 16. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 17. { 18. return rates_total; 19. }; 20. //+------------------------------------------------------------------+ 21. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 22. { 23. static int p = 0; 24. MqlRates rate[1]; 25. 26. switch(id) 27. { 28. case CHARTEVENT_KEYDOWN: 29. switch ((int)lparam) 30. { 31. case def_KEY_DOWN: 32. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 33. break; 34. case def_KEY_UP: 35. p = (p > 0 ? p - 1 : p); 36. break; 37. default: 38. return; 39. } 40. Comment(StringFormat("Current bar analyzed: %d", p)); 41. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 42. ObjectMove(0, def_NameObj, 0, rate[0].time, rate[0].close); 43. break; 44. } 45. ChartRedraw(); 46. }; 47. //+------------------------------------------------------------------+ 48. void OnDeinit(const int reason) 49. { 50. Comment(""); 51. ObjectDelete(0, def_NameObj); 52. ChartRedraw(); 53. }; 54. //+------------------------------------------------------------------+
Code 03
Code 03 ist etwas komplexer als die anderen Codes, die im heutigen Artikel behandelt wurden. Trotzdem gibt es hier kaum etwas Neues. Deshalb werden wir uns nur auf die neuen Punkte konzentrieren und den Rest außer Acht lassen, da die übrigen Teile leichter zu verstehen sind.
In Zeile 23 starten wir also einen lokalen Zähler. Damit sehen wir, um wie viele Bars wir gegenüber der neuesten Bar auf dem Chart verschoben sind. Um dies zu verdeutlichen, haben wir in Zeile 40 bestimmte Informationen direkt auf dem Chart angezeigt. Der Code zwischen den Zeilen 29 und 39 wird bereits verwendet, um sicherzustellen, dass nur die zuvor definierten Tasten erfasst und verarbeitet werden. In Zeile 41 werden nun die Kursdaten der Bar abgerufen, auf die der Zähler p aktuell verweist. Bitte beachten Sie, dass wir nur eine Bar abrufen. In Zeile 42 wird das Objekt dann auf dem Chart verschoben. Wenn dieser Code also ausgeführt wird, erhalten wir das Ergebnis, das in der folgenden Animation zu sehen ist.

Animation 03
Das war interessant, aber auch ein wenig verwirrend, weil wir die Bars zählen mussten, um zu verstehen, welcher gerade verwendet wird. Allerdings hätten wir fast vergessen zu erwähnen, was Zeile 50 tut. Wenn wir den Indikator aus dem Chart entfernen, verwenden wir Zeile 50, um den dort vorhandenen alten Text zu löschen. Das ist ein ganz einfacher Vorgang, den manche Leute vergessen, was dazu führt, dass der Chart unnötig überladen wirkt.
Bisher läuft alles gut. Wir wissen bereits, wie man kleine und einfache Manipulationen an einem Objekt vornimmt. Es gibt jedoch eine Sache, die wir nun lernen müssen, denn wie Sie wahrscheinlich bemerkt haben, haben bisher alle Objekte denselben Namen erhalten, was weder besonders interessant noch praktisch ist. Und da jedes Objekt auf einem Chart einen eindeutigen Namen haben muss, müssen wir eine Möglichkeit finden, mehrere Objekte gleichzeitig auf dem Chart zu platzieren. Um zu erklären, wie das funktioniert, wenden wir uns nun einem neuen Thema zu.
Mehr als ein Objekt auf demselben Chart
Es gibt ein Problem, das viele Anfänger ratlos macht: Sie verstehen nicht vollständig, wie der Code funktioniert, interessieren sich aber dafür. Und wenn wir beschließen, verschiedene Codeabschnitte zu kombinieren oder sogar verschiedene Programme auf demselben Chart zu verwenden, von denen jedes dazu dient, ein bestimmtes Objekt auf dem Chart zu erzeugen, tritt irgendwann ein Problem auf. Und ich drücke mich noch milde aus, wenn ich sage, dass der Benutzer die Objekte nicht manuell in den Chart eingefügt hat. Ich berücksichtige lediglich den Fall, dass der Benutzer versucht, Code zu verwenden, der dies für ihn erledigt.
Das Problem ist, dass jedes Objekt auf einem Chart einen eindeutigen Namen haben MUSS, um sowohl den Entwicklern als auch der MetaTrader 5-Plattform selbst die Arbeit zu erleichtern. Wenn wir versuchen, ein Objekt mit einem Namen zu erstellen, der auf dem Chart bereits vorhanden ist, führt dies zu Problemen. Das liegt daran, dass wir, wenn wir versuchen, etwas zu manipulieren, das eigentlich gar nicht auf dem Chart vorhanden ist, dennoch etwas anderes manipulieren, das auf dem Chart vorhanden ist. Das sorgt bei Programmieranfängern oder auch bei weniger erfahrenen Nutzern für enorme Verwirrung.
Da Programme oder Anwendungen in den meisten Fällen für bestimmte Zwecke entwickelt werden, machen sich die Entwickler im Grunde keine Mühe, den Benutzern zu erklären, dass sie mit den Namen der auf dem Chart vorhandenen Objekte vorsichtig umgehen sollten. Grundsätzlich geht jeder Entwickler davon aus, dass kein Benutzer den Fehler begeht, mehrere Anwendungen zu einer einzigen zusammenzufassen oder gar verschiedene Anwendungen gleichzeitig auf demselben Chart zu verwenden.
Aber um auf das Programmieren zurückzukommen: Das eigentliche Problem entsteht dadurch, dass viele Anfänger ausschließlich die Copy-and-Paste-Technik anwenden, ohne wirklich zu verstehen, was sie im Code tun und welche Folgen diese Aktionen zur Laufzeit haben. Um zu veranschaulichen, welche Probleme diese von vielen Anfängern verwendete Copy-and-Paste-Methode verursachen kann, erstellen wir ein kleines Codebeispiel. Das ist unten zu sehen.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameObj "Demo" 05. #define def_KEY_UP 38 06. #define def_KEY_DOWN 40 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. ObjectCreate(0, def_NameObj, OBJ_VLINE, 0, 0, 0); 11. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrRoyalBlue); 12. ObjectCreate(0, def_NameObj, OBJ_HLINE, 0, 0, 0); 13. ObjectSetInteger(0, def_NameObj, OBJPROP_COLOR, clrPurple); 14. 15. return INIT_SUCCEEDED; 16. }; 17. //+------------------------------------------------------------------+ 18. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 19. { 20. return rates_total; 21. }; 22. //+------------------------------------------------------------------+ 23. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 24. { 25. static int p = 0; 26. MqlRates rate[1]; 27. 28. switch(id) 29. { 30. case CHARTEVENT_KEYDOWN: 31. switch ((int)lparam) 32. { 33. case def_KEY_DOWN: 34. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 35. break; 36. case def_KEY_UP: 37. p = (p > 0 ? p - 1 : p); 38. break; 39. default: 40. return; 41. } 42. Comment(StringFormat("Current bar analyzed: %d", p)); 43. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 44. ObjectMove(0, def_NameObj, 0, rate[0].time, rate[0].close); 45. ObjectMove(0, def_NameObj, 0, rate[0].time, rate[0].close); 46. break; 47. } 48. ChartRedraw(); 49. }; 50. //+------------------------------------------------------------------+ 51. void OnDeinit(const int reason) 52. { 53. Comment(""); 54. ObjectDelete(0, def_NameObj); 55. ChartRedraw(); 56. }; 57. //+------------------------------------------------------------------+
Code 04
Hier zeigen wir einen eher groben, aber sehr häufigen Fehler, den Programmieranfänger machen, da ihr Ziel offenbar darin besteht, ein Kreuz auf dem Chart zu erzeugen. Wenn dieser Code jedoch ausgeführt wird, sieht das Ergebnis wie folgt aus:

Animation 04
Hmm, seltsam. Im OnInit-Ereignisbehandler erstellen wir zwei Objekte: eines ist eine horizontale Linie und das andere eine vertikale Linie. Warum wird dann nur die vertikale Linie angezeigt? Nun, das ist noch nicht alles. Wenn man genau hinschaut, erkennt man, dass die vertikale Linie ursprünglich nicht die in Animation 04 gezeigte Farbe haben sollte. Was passiert hier eigentlich?
Das Problem ist, dass wir zwar angeben, dass zwei Objekte erstellt werden sollen – eines in Zeile 10 und das andere in Zeile 12 –, jedoch nur das in Zeile 10 deklarierte Objekt erstellt wird. Dies geschieht, weil MetaTrader 5 bei der Ausführung von Zeile 12 meldet, dass der Chart bereits ein Objekt mit demselben Namen enthält wie das in Zeile 12 definierte Objekt. Tatsächlich befindet es sich noch nicht auf dem Chart, sondern in der Ausführungswarteschlange und ist bereit, so bald wie möglich auf dem Chart platziert zu werden.
In jedem Fall wird die Erstellung des in Zeile 12 deklarierten Objekts abgelehnt. Da der Großteil des Codes relativ einfach ist, ignorieren viele Leute solche Details letztendlich, und schlimmer noch: Oft fehlt ihnen das Interesse, sich mit der Dokumentation auseinanderzusetzen. Das liegt jedoch hauptsächlich an mangelnder Erfahrung: Ein Programmieranfänger denkt schließlich, dass der Fehler vielleicht woanders liegt. Wenn dies jedoch einem Benutzer passiert, der versucht, zwei oder mehr Anwendungen gleichzeitig auf demselben Chart zu nutzen, und die Objektnamen übereinstimmen, wird die Situation wirklich verwirrend. Auch wenn solche Fälle äußerst selten sind, kann es vorkommen, dass Sie mit dieser Situation konfrontiert werden.
Es gibt verschiedene Methoden für den Umgang mit mehreren Objekten auf einem Chart, aber wenn wir versuchen, ein Objekt mit demselben Namen wie ein bereits auf dem Chart vorhandenes Objekt zu erstellen, kann es in jedem Fall schwierig sein, die Fehler zu behandeln, die früher oder später auftreten. In manchen Fällen können wir verschiedene Objekte mit demselben Namen verwenden, jedoch nicht gleichzeitig. Da es sich hierbei um eine sehr spezielle Situation handelt, werden wir vorerst nicht näher darauf eingehen, um diejenigen nicht zu verwirren, die versuchen, die Vorgehensweise richtig zu erlernen und so seltsame Fehler zu vermeiden.
Auf jeden Fall ist eine der einfachsten Lösungen, die in fast allen Situationen funktioniert, die Erstellung eines Arrays aus Objekten, wobei jedes auf dem Chart platzierte Objekt durch ein Element dieses Arrays dargestellt wird. Meiner Meinung nach ist dies vielleicht die einfachste und universellste Lösung, die man sich derzeit vorstellen kann. Somit kann derselbe Code 04 wie folgt geändert werden:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 14. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 15. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 16. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 17. 18. return INIT_SUCCEEDED; 19. }; 20. //+------------------------------------------------------------------+ 21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 22. { 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch(id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. switch ((int)lparam) 35. { 36. case def_KEY_DOWN: 37. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 38. break; 39. case def_KEY_UP: 40. p = (p > 0 ? p - 1 : p); 41. break; 42. default: 43. return; 44. } 45. Comment(StringFormat("Current bar analyzed: %d", p)); 46. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 47. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 48. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 49. break; 50. } 51. ChartRedraw(); 52. }; 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. Comment(""); 57. for (uint c = 0; c < gl_Objs.Size(); c++) 58. ObjectDelete(0, gl_Objs[c]); 59. ChartRedraw(); 60. }; 61. //+------------------------------------------------------------------+
Code 05
Natürlich gehen wir hier so einfach wie möglich vor. Jeder, der die Artikel verfolgt und sie in die Praxis umgesetzt hat, weiß, dass es mit dem derzeitigen Wissensstand möglich ist, eine weitaus komplexere Lösung zu entwickeln. Was uns jedoch wirklich interessiert, ist immer das Endergebnis. Dies ist in der folgenden Animation zu sehen:

Animation 05
Bitte beachten Sie, dass sich das Ergebnis völlig von dem unterscheidet, was in Animation 04 gezeigt wurde. Es ist jedoch wichtig zu beachten: Wenn wir Code 05 mit Code 04 vergleichen, können wir feststellen, dass wir fast keine Änderungen am Code vorgenommen haben, um das durch Code 04 verursachte Problem zu beheben. Die auffälligste Änderung ist die Deklaration, die in Zeile 09 von Code 05 zu sehen ist. Da dies allein jedoch nicht ausreichen würde, um alle Probleme zu lösen, war eine weitere Änderung erforderlich. Dies ist in Zeile 07 von Code 05 zu sehen.
Bitte beachten Sie, dass wir in diesem Fall die Objektname-Definition in ein Makro geändert haben. In diesem Makro geschieht etwas wirklich Wichtiges: die Verwendung der ObjectsTotal-Funktion. Diese Funktion aus der MQL5-Standardbibliothek gibt an, wie viele Objekte auf dem Chart vorhanden sind oder sich in der Ausführungswarteschlange befinden, um auf dem Chart platziert zu werden. Dadurch erhält jedes Objekt, ganz gleich, wie viele Objekte wir auf dem Chart platzieren, immer einen eindeutigen Namen – selbst wenn wir verschiedene Programme von unterschiedlichen Entwicklern verwenden. Wenn wir ein Objekt löschen und anschließend ein neues erstellen – selbst wenn ihre Namen sehr ähnlich sind –, sind beide Objekte gerade wegen der Funktion ObjectsTotal, die wir im Makro sehen, eindeutig.
Eine ganz einfache, aber sehr wirkungsvolle Sache. Was den Rest betrifft, so sind die vorgenommenen Änderungen für jeden, der die Artikel durchgearbeitet hat, sehr leicht nachvollziehbar, sodass es nicht notwendig ist, näher darauf einzugehen. Trotz dieser Einfachheit sollte Folgendes nicht vergessen werden: Da das Makro immer einen eindeutigen Namen erzeugt, müssen wir den Namen des bereits erstellten Objekts irgendwo speichern. In diesem Fall wird der Name in dem Array aus Zeile 09 gespeichert. Ohne dies wären wir nicht in der Lage, das Objekt zu finden, das bereits auf dem Chart vorhanden ist.
Bevor ich diesen Artikel beende, möchte ich noch eine Sache erläutern, die wir in einigen kommenden Artikeln sehr häufig verwenden werden. Um die Themen jedoch klar voneinander abzugrenzen, wollen wir nun zu einem neuen Abschnitt übergehen.
Kurzname
Bis hierhin sind alle gezeigten Codeabschnitte praktisch wie ein einziger Code, und in der Regel sollten sie nacheinander verwendet werden. In der Praxis und unter realen Bedingungen ist jedoch nicht immer alles so einfach und angenehm. Deshalb müssen wir als Programmierer bestimmte Vorsichtsmaßnahmen treffen und wissen, wie man bestimmte Dinge macht. Dazu gehört unter anderem die Notwendigkeit, auf den auf dem Chart angezeigten Indikator zugreifen zu können. Den Grund dafür werden wir später sehen, wenn wir damit beginnen, einige, sagen wir mal, komplexere Dinge zu entwickeln.
Als Benutzer denken Sie vielleicht: „Aber natürlich weiß ich, wie ich auf den Indikator zugreifen kann; ich muss nur STRG + I drücken, und schon öffnet sich ein Fenster, über das ich auf die auf dem Chart vorhandenen Indikatoren zugreifen kann. Und wenn ich ihn auf dem Chart platzieren möchte, muss ich lediglich zum Navigator gehen, den gewünschten Indikator suchen – und das war’s schon. Dann ziehe ich ihn auf den Chart, und MetaTrader 5 erledigt den Rest. Einfach.“
Na gut, wie wir bereits gesagt haben, ist das die Sichtweise des Benutzers. Als Programmierer verfolgen wir jedoch andere Arbeitsansätze, und wir benötigen eine andere Perspektive, um zu verstehen, wie diese Dinge funktionieren. In diesem Fall hilft uns eine solche Benutzeroberfläche überhaupt nicht weiter; im Gegenteil, sie könnte es sogar erschweren, bestimmte Details zu verstehen, auf die wir später noch eingehen werden.
Und da ich möchte, dass Sie sich allmählich daran gewöhnen, wie Programmierer zu denken, müssen wir noch ein wenig darüber sprechen, wie ein Programmierer die Möglichkeiten und Grenzen von MetaTrader 5 einschätzen würde. Außerdem müssen wir verstehen, wie MetaTrader 5 erkennt, was wir in den MQL5-Code eingeben. Tatsache ist, dass MQL5 entwickelt wurde, um MetaTrader 5 mitzuteilen, was wir tun möchten.
Im Gegensatz dazu, wie ein Benutzer eine Anwendung sieht, die in MetaTrader 5 auf einem Chart läuft, haben wir Programmierer die Möglichkeit, bei MetaTrader 5 Informationen darüber abzufragen, welche Anwendungen derzeit laufen. Dadurch können wir Anwendungen automatisch zur Liste hinzufügen oder sogar daraus entfernen. In den meisten Fällen besteht das Problem darin, die auf dem Chart angezeigten Indikatoren zu identifizieren. Das liegt daran, dass ein Indikator – entgegen der landläufigen Meinung – einen Namen haben kann, den der Code intern verwendet und der sich völlig vom Namen der ausführbaren Datei der Anwendung unterscheidet.
Und dieser Name, der im Code sichtbar ist, kann – oder vielmehr sollte – vom Benutzer nicht geändert werden. Allerdings können Sie als Programmierer dies natürlich zulassen. Davon ist jedoch abzuraten, um Probleme zu vermeiden, deren Art sich derzeit nur schwer erklären ließe.
Das bedeutet also, dass wir unserer Anwendung bei der Arbeit mit Indikatoren einen Namen zuweisen können. Also gut, wie machen wir das? Dies geschieht mithilfe einer Funktion, die in der MQL5-Standardbibliothek verfügbar ist: IndicatorSetString. Auch wenn dies trivial und sogar unnötig erscheinen mag, wird die Vergabe eines Kurznamens für unseren Indikator eine Reihe von Aufgaben erheblich vereinfachen. Diese Funktion ist nicht nur für die Vergabe eines Indikatornamens nützlich. Sie dient zudem anderen, noch wichtigeren Zwecken. Aber schauen wir uns zunächst einmal an, wie wir den internen Namen für unseren Indikator definieren. Das geht etwa so:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. IndicatorSetString(INDICATOR_SHORTNAME, "Version Demo"); 07. 08. return INIT_SUCCEEDED; 09. }; 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. return rates_total; 14. }; 15. //+------------------------------------------------------------------+
Code 06
Es spielt keine Rolle, welchen Namen der Benutzer der ausführbaren Datei aus Code 06 gibt. Letztendlich interessiert uns der in Zeile 06 angegebene Name. Als Indikatorname kann alles festgelegt werden. Es ist jedoch ratsam, einen passenden Namen zu wählen, der den Zweck des Indikators in wenigen Worten beschreibt. Das bezeichnen wir als Kurznamen.
Aber warum sollte man solche Dinge überhaupt definieren? Wozu dient das? Nun, das an dieser Stelle zu erklären, ist etwas schwierig. Um wirklich verständlich zu erklären, warum wir für unsere Indikatoren einen Kurznamen festlegen müssen, müssten wir etwas zeigen, das die Situation zum jetzigen Zeitpunkt eher verkomplizieren als verdeutlichen würde. Ich möchte jedoch, dass Sie stets versuchen, jedem Indikator, den Sie implementieren möchten, einen kurzen und aussagekräftigen Namen zu geben, denn das erspart Ihnen später die Mühe, sich solche Namen ausdenken zu müssen, wenn Sie mit dem angezeigten Indikator arbeiten müssen. Auf jeden Fall handelt es sich nur um eine einzige Codezeile, die Ihnen in Zukunft viele Probleme ersparen wird.
Abschließende Gedanken
Im heutigen Artikel haben wir uns damit beschäftigt, wie sich bestimmte Objekte auf einfache Weise steuern lassen. Wir haben gesehen, wie mithilfe einer benutzerdefinierten Anwendung mehrere Objekte im selben Chart platziert werden können. Außerdem haben wir begonnen zu erkennen, wie wichtig es ist, jedem Indikator, den wir implementieren wollen, einen Kurznamen zuzuweisen.
All diese Konzepte, die auf den ersten Blick vielleicht nicht ganz miteinander in Zusammenhang zu stehen scheinen, sollten als zusammenhängendes Ganzes betrachtet werden. Auf jeden Fall sollte der heutige Artikel lediglich eine einfache Methode zur Lösung verschiedener Probleme aufzeigen, denn wenn wir mit dem Lernen beginnen, machen wir oft viele Fehler, die – obwohl sie einfach sind und leicht vermieden werden können – unsere Motivation völlig untergraben. Wir fangen an zu glauben, dass Programmieren nur etwas für einige wenige Auserwählte ist, obwohl eigentlich jeder das Programmieren lernen sollte, um seine Ideen zum Leben zu erwecken.
Für alle Fälle lasse ich die hier gezeigten Codes im Anhang, damit Sie üben und lernen können und nicht dieselben Fehler machen, die wir alle machen, wenn wir etwas Neues lernen.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/15987
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.