English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems (Teil 59): Eine neue Zukunft

Entwicklung eines Replay-Systems (Teil 59): Eine neue Zukunft

MetaTrader 5Beispiele | 26 Februar 2025, 09:20
90 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „ Entwicklung eines Replay-Systems (Teil 58): Wiederaufnahme der Arbeit am Dienst“ habe ich erwähnt, dass das System einige Änderungen erfahren hat und dass es einen Grund gibt, eine kleine Verzögerung zwischen dem Anwenden einer Vorlage auf einen Zeitplan und der Aktualisierung des Zeitplans durch den Dienst einzuplanen. Geschieht dies nicht, erzwingen die Module eine vorzeitige Schließung des Dienstes. Einige von Ihnen fragen sich vielleicht, wie das passieren konnte. In diesem Artikel werden wir genauer darauf eingehen.

Um dies richtig zu erklären und sicherzustellen, dass Sie das Konzept vollständig verstehen, schauen Sie sich bitte das folgende Video 01 an.


Demonstrationsvideo: Wie Dinge schiefgehen können

Dieses Video bietet eine unbearbeitete, unveränderte Demonstration dessen, was tatsächlich passiert. Aber warum tritt dieses Problem auf? Um die Erklärung zu verstehen, ist es wichtig, die wichtigsten Punkte des vorherigen Artikels noch einmal durchzugehen. Ich werde sie hier kurz zusammenfassen.


Verstehen, warum der Dienst beendet wird

Es gibt einen bestimmten Grund, warum der Dienst vorzeitig beendet wird, sobald MetaTrader 5 die Vorlage anwendet. Die Erklärung ist einfach: Der Indikator – oder genauer gesagt das Steuermodul – sorgt für die Schließung des Charts. Immer noch unklar? Lassen Sie uns den relevanten Abschnitt des Codes des Kontrollindikators untersuchen, der für dieses Verhalten verantwortlich ist. Den Codeausschnitt findet ihr weiter unten.

53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay // simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }

Codefragment des Steuergeräts

Beachten Sie Zeile 63, in der MetaTrader 5 durch einen Aufruf angewiesen wird, das Chart zu schließen. Doch wer tätigt diesen Anruf eigentlich? Wenn es ausschließlich durch die Änderung der Vorlage ausgelöst würde, würde die Meldung in Zeile 58 im Meldungsfeld von MetaTrader 5 erscheinen. Wie Sie möglicherweise bemerkt haben, wird diese Nachricht jedoch nicht gedruckt. Dies lässt darauf schließen, dass die Schließung des Charts nicht direkt durch die Änderung der Vorlage verursacht wurde.

Ich verstehe, warum Sie dies in Frage stellen und sogar an der Erklärung zweifeln. Tatsächlich wird jedoch durch die Anwendung der Vorlage in MetaTrader 5 der Kontrollindikator aus dem Chart entfernt, was letztendlich zur Schließung des Charts führt. Der entscheidende Punkt ist, dass dieser Prozess asynchron erfolgt, was bedeutet, dass sich die Ereignisse nicht in der genauen Reihenfolge entwickeln, die Sie vielleicht erwarten.

Was tatsächlich passiert, ist, dass MetaTrader 5 irgendwann die Anwendung der Vorlage abschließt. In diesem Fall sendet MetaTrader 5 ein Deinit-Ereignis an den Indikator, das von der Funktion OnDeinit verarbeitet wird. Anders als man vielleicht erwartet, enthält die Variable „reason“ jedoch nicht den Wert REASON_TEMPLATE, sondern REASON_REMOVE. Dies liegt daran, dass MetaTrader 5 im Rahmen des Vorlagenanwendungsprozesses den Kontrollindikator aktiv aus dem Chart entfernt. Aus diesem Grund wird die Nachricht ab Zeile 58 nie im Nachrichtenfenster des Terminals gedruckt.

Es stellt sich natürlich die Frage: Warum wendet MetaTrader 5 Vorlagen nicht synchron an? Mit anderen Worten, warum wird die Vorlage nicht sofort und vollständig auf das Chart angewendet, wenn die Funktion ChartApplyTemplate ausgeführt wird, die sich in Zeile 87 der Header-Datei C_Replay.mqh befindet? Die Antwort liegt in der Leistung. Eine Vorlage kann mehrere Indikatoren oder sogar einen Expert Advisor enthalten, der eine bestimmte Anzahl an Balken im Chart erfordert, bevor er nützliche Daten liefert.

Wenn MetaTrader 5 auf die synchrone Fertigstellung von ChartApplyTemplate warten müsste, könnte die Plattform für mehrere Sekunden einfrieren oder sogar abstürzen, wenn etwas in der Vorlage einen kritischen Fehler verursacht. Durch die asynchrone Handhabung dieses Prozesses verbessert MetaTrader 5 die Leistung, stellt jedoch auch weniger erfahrene Programmierer vor Herausforderungen. Eine dieser Herausforderungen besteht darin, zu verstehen, welche Funktionen asynchron und welche sofort ausgeführt werden.

Da das Erzwingen einer sofortigen Chartaktualisierung mit ChartRedraw nicht gewährleistet, dass die Vorlage sofort angewendet wird, benötigen wir einen alternativen Ansatz. Das Demonstrationsvideo veranschaulicht die implementierte Lösung.

Sie fragen sich vielleicht: Warum lösen Sie dieses Problem nicht dadurch, dass Sie den Kontrollindikator direkt in die Vorlage aufnehmen? Dadurch ließe sich zwar das Problem der vorzeitigen Schließung vermeiden, es würde jedoch ein anderes Problem entstehen. In früheren Versionen haben wir eine globale Terminalvariable verwendet, um zu verhindern, dass der Kontrollindikator in unbeabsichtigten Charts erscheint. Allerdings verlassen wir uns nicht mehr auf diese Methode. Stattdessen verwenden wir jetzt einen anderen Sperrmechanismus. Dies bedeutet, dass wir nicht zulassen können, dass die Vorlage den Kontrollindikator enthält, da dies zu Komplikationen führen würde, die nur schwer zu lösen wären.

Wie im vorherigen Artikel erwähnt, wurden sowohl am Steuerungs- als auch am Mausmodul Änderungen vorgenommen. Dies war das Ergebnis der Anpassungen im Zuge unserer Neuausrichtung auf den Replay-/Simulationsdienst. Schauen wir uns nun den aktualisierten Code für beide Indikatoren an und konzentrieren uns dabei auf die wichtigsten Änderungen.


Aktualisierter Code für den Replay-/Simulationsdienst

Nachfolgend finden Sie die neueste Version des Quellcodes des Steuermoduls.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.59"
07. #property link "https://www.mql5.com/en/articles/12075"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal = 0;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    ResetLastError();   
24.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
25.       SetUserError(C_Terminal::ERR_PointerInvalid);
26.    if ((_LastError != ERR_SUCCESS) || (user00 == 0))
27.    {
28.       Print("Control indicator failed on initialization.");
29.       return INIT_FAILED;
30.    }
31.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
32.    ArrayInitialize(m_Buff, EMPTY_VALUE);
33.    
34.    return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.    return m_RatesTotal = rates_total;
40. }
41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.    (*control).DispatchMessage(id, lparam, dparam, sparam);
45.    if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown)
46.    {
47.       Print("Internal failure in the messaging system...");
48.       ChartClose(user00);
49.    }
50.    (*control).SetBuffer(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+
53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay // simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }
68. //+------------------------------------------------------------------+
69. 

Quellcode des Kontrollindikators

Ihnen ist vielleicht aufgefallen, dass ich den Code nicht in der Header-Datei zeige, weil er nicht geändert wurde. Obwohl wir uns oben den gesamten Code des Steuermoduls ansehen können, konzentrieren wir uns auf die geänderten Teile. Der einzige Unterschied besteht in Zeile 26. Dementsprechend überprüfen wir zwei Bedingungen. Einer davon ist der Wert von _LastError und der andere ist der von user00. Dieser Nutzer „user00“ wird in Zeile 16 definiert und ist dafür verantwortlich, vom Wiedergabe-/Simulationsdienst die ID des Charts zu erhalten, auf dem das Steuermodul ausgeführt werden soll.

Jetzt seien Sie bitte vorsichtig. Typischerweise können wir als Nutzer nicht bestimmen, welcher Wert am Anfang der Zeile 16 zugewiesen werden soll. Es passiert ganz natürlich. Was aber, wenn wir versuchen, das System auszutricksen und die gesamte Konfiguration als Vorlagedatei zu speichern? Damit könnte alles klappen und wir würden uns die Mühe sparen, eine Vorlage zu verwenden, oder? Nein. Das wird nicht wie erwartet funktionieren.

Wenn wir, nachdem der Dienst alles geladen hat und MetaTrader 5 das Chart stabilisiert hat, dieses fertige Chart als Vorlage speichern, werden wir beim Öffnen der Vorlagendatei etwas bemerken. Dies ist aus dem folgenden Fragment ersichtlich.

01. <indicator>
02. name=Custom Indicator
03. path=Services\Market Replay.ex5::Indicators\Market Replay.ex5
04. apply=0
05. show_data=1
06. scale_inherit=0
07. scale_line=0
08. scale_line_percent=50
09. scale_line_value=0.000000
10. scale_fix_min=0
11. scale_fix_min_val=0.000000
12. scale_fix_max=0
13. scale_fix_max_val=0.000000
14. expertmode=1610613824
15. fixed_height=-1
16. 
17. <graph>
18. name=
19. draw=0
20. style=0
21. width=1
22. color=
23. </graph>
24. <inputs>
25. user00=130652731570824061
26. </inputs>
27. </indicator>

Dateifragment der Vorlage

Die genaue Position dieses Codefragments innerhalb der Datei ist hierbei nicht relevant. Die Zeilennummern dienen lediglich als Referenz und sollen uns helfen, die Dinge klarer zu erklären.

Beachten Sie, dass Zeile 1 ein öffnendes Tag darstellt, während Zeile 27 die Struktur abschließt. Zwischen diesen beiden Tags werden verschiedene Details angegeben, einschließlich der Position des auf das Chart anzuwendenden Indikators, die in Zeile 3 angegeben ist. Großartig. Achten Sie nun genau auf Zeile 24. Hier öffnet ein Tag den Abschnitt für die Eingabeparameter, die der in Zeile 3 definierte Indikator erwartet. In ähnlicher Weise markiert Zeile 26 den Abschluss dieses Abschnitts.

In Zeile 25 verweisen wir auf einen Eingabeparameter, der im Quellcode des Indikators in Zeile 16 deklariert ist, um die Chart-ID zu erhalten. Diese Zeile weist dem Kontrollindikator einen Wert zu, sodass die in Zeile 26 des Quellcodes des Indikators getestete Bedingung als „false“ ausgewertet wird und der Indikator ordnungsgemäß geladen werden und funktionieren kann. Richtig? Leider nein. Wenn der Wiedergabe-/Simulationsdienst den Kontrollindikator korrekt auf das Chart anwendet, erkennt der Indikatorcode, dass bereits eine andere Instanz ausgeführt wird. Wenn dies geschieht, erzeugt Zeile 24 im Kontrollindikatorcode ein Ergebnis, das Zeile 25 auslöst. Folglich enthält _LastError in Zeile 26 nicht mehr den erwarteten Wert, was zu einem Systemfehler führt. Als Ergebnis generiert MetaTrader 5 ein Deinit-Ereignis, das von der Prozedur OnDeinit in Zeile 53 behandelt wird. An diesem Punkt wird die Ausführung mit REASON_INITFAILED fortgesetzt, das Chart geschlossen und der Wiedergabe-/Simulationsdienst beendet.

Dies zeigt, wie alles reibungslos funktioniert, wenn wir die Funktionen der MetaTrader 5-Plattform vollständig nutzen. Aber das war das Problem mit dem Kontrollindikator. Was ist mit dem Mausindikator?

Die Situation mit dem Mauszeiger ist etwas interessanter. Es wurde entschieden, dass dieses Modul eine bestimmte, jedoch nicht ausschließliche Aktion ausführen darf. Vielmehr wäre es das erste Land, das diesen Prozess in Gang setzt. Aus diesem Grund waren einige Ergänzungen und Entfernungen innerhalb des Mausanzeigemoduls notwendig. Um diese Anpassungen richtig zu definieren, behandeln wir sie im nächsten Abschnitt.


Kleine Änderungen für signifikante Verbesserungen

Obwohl das Mausmodul ursprünglich für die Platzierung im Hauptfenster vorgesehen war, können wir mit MQL5 noch viel mehr tun. Ehrlich gesagt hatte ich zunächst nicht daran gedacht, bestimmte Funktionsweisen zu implementieren. Da MetaTrader 5 jedoch Konfigurations- und Anpassungsoptionen bietet, die es uns ermöglichen, bestimmte Aktionen effizienter auszuführen, habe ich mich entschieden, einige Änderungen einzuführen. Der folgende Abschnitt stellt die vorgenommenen Aktualisierungen und Ergänzungen vor, beginnend mit der unten gezeigten Klasse C_Terminal.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Macros.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. class C_Terminal
008. {
009. //+------------------------------------------------------------------+
010.    protected:
011.       enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance};
012. //+------------------------------------------------------------------+
013.       struct st_Terminal
014.       {
015.          ENUM_SYMBOL_CHART_MODE    ChartMode;
016.          ENUM_ACCOUNT_MARGIN_MODE  TypeAccount;
017.          long           ID;
018.          string         szSymbol;
019.          int            Width,
020.                         Height,
021.                         nDigits,
022.                         SubWin;
023.          double         PointPerTick,
024.                         ValuePerPoint,
025.                         VolumeMinimal,
026.                         AdjustToTrade;
027.       };
028. //+------------------------------------------------------------------+
029.    private   :
030.       st_Terminal m_Infos;
031.       struct mem
032.       {
033.          long   Show_Descr,
034.                 Show_Date;
035.          bool   AccountLock;
036.       }m_Mem;
037. //+------------------------------------------------------------------+
038.       void CurrentSymbol(void)
039.          {
040.             MqlDateTime mdt1;
041.             string sz0, sz1;
042.             datetime dt = macroGetDate(TimeCurrent(mdt1));
043.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
044.       
045.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
046.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
047.             switch (eTS)
048.             {
049.                case DOL   :
050.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
051.                case IND   :
052.                case WIN   : sz1 = "GJMQVZ";       break;
053.                default    : return;
054.             }
055.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
056.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
057.          }
058. //+------------------------------------------------------------------+
059.    public   :
060. //+------------------------------------------------------------------+      
061.       C_Terminal(const long id = 0, const uchar sub = 0)
062.          {
063.             m_Infos.ID = (id == 0 ? ChartID() : id);
064.             m_Mem.AccountLock = false;
065.             m_Infos.SubWin = (int) sub;
066.             CurrentSymbol();
067.             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
068.             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
069.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
070.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
071.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
072.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
073.             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
074.             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
075.             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
076.             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
077.             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
078.             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
079.             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
080.             m_Infos.ChartMode   = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
081.             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
082.             ResetLastError();
083.          }
084. //+------------------------------------------------------------------+
085.       ~C_Terminal()
086.          {
087.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
088.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
089.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false);
090.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false);
091.          }
092. //+------------------------------------------------------------------+
093. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
094.          {
095.             if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
096.             m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
097.          }
098. //+------------------------------------------------------------------+
099. inline const st_Terminal GetInfoTerminal(void) const
100.          {
101.             return m_Infos;
102.          }
103. //+------------------------------------------------------------------+
104. const double AdjustPrice(const double arg) const
105.          {
106.             return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits);
107.          }
108. //+------------------------------------------------------------------+
109. inline datetime AdjustTime(const datetime arg)
110.          {
111.             int nSeconds= PeriodSeconds();
112.             datetime   dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
113.             
114.             return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
115.          }
116. //+------------------------------------------------------------------+
117. inline double FinanceToPoints(const double Finance, const uint Leverage)
118.          {
119.             double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
120.             
121.             return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
122.          };
123. //+------------------------------------------------------------------+
124.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
125.          {
126.             static string st_str = "";
127.             
128.             switch (id)
129.             {
130.                case CHARTEVENT_CHART_CHANGE:
131.                   m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
132.                   m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
133.                   break;
134.                case CHARTEVENT_OBJECT_CLICK:
135.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
136.                   if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
137.                      ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
138.                   break;
139.                case CHARTEVENT_OBJECT_CREATE:
140.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
141.                   st_str = sparam;
142.                   break;
143.             }
144.          }
145. //+------------------------------------------------------------------+
146. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const
147.          {
148.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
149.             ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0);
150.             ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
151.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
152.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
153.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
154.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
155.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
156.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
157.          }
158. //+------------------------------------------------------------------+
159.       bool IndicatorCheckPass(const string szShortName)
160.          {
161.             string szTmp = szShortName + "_TMP";
162.             
163.             if (_LastError != ERR_SUCCESS) return false;            
164.             IndicatorSetString(INDICATOR_SHORTNAME, szTmp);
165.             m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin);
166.             if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE)
167.             {
168.                ChartIndicatorDelete(m_Infos.ID, 0, szTmp);
169.                Print("Only one instance is allowed...");
170.                SetUserError(C_Terminal::ERR_NoMoreInstance);
171.                
172.                return false;
173.             }
174.             IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
175.             ResetLastError();
176.    
177.             return true;
178.          }
179. //+------------------------------------------------------------------+
180. };

Quellcode der Header-Datei C_Terminal.mqh

Beim Überprüfen des Codes in der Header-Datei, die die Klasse C_Terminal enthält, bemerken Sie die geringfügigen Änderungen möglicherweise nicht sofort. Diese Änderungen sind geringfügig, ermöglichen uns jedoch, viel zu erreichen, insbesondere Aufgaben, die wir in naher Zukunft erledigen müssen.

Alle Änderungen ergeben sich aus Zeile 22, wo eine neue Variable eingeführt wurde. Diese Variable existierte zuvor nicht, wurde jetzt aber hinzugefügt, damit wir Objekte an ein bestimmtes Unterfenster weiterleiten können.

Da auf diese Variable außerhalb der Klasse zugegriffen werden kann, müssen wir sicherstellen, dass sie einen geeigneten Anfangswert hat. Um dies zu erreichen, haben wir in Zeile 61 eine Änderung am Klassenkonstruktor vorgenommen. Nun erhält der Konstruktor einen zusätzlichen Parameter, der Werte zwischen 0 und 255 enthalten kann. Dieser Bereich ist mehr als ausreichend, wenn man bedenkt, dass Charts selten mehr als zwei oder drei Unterfenster enthalten. Es muss jedoch auf ein wichtiges Detail eingegangen werden. In Zeile 65 behandeln wir dies mit einer expliziten Typkonvertierung. Aber warum deklariert man die Variable nicht von Anfang an als uchar, anstatt sie hier zu konvertieren? Die Antwort liegt in der Abwärtskompatibilität. MQL5 erwartet einen vorzeichenbehafteten Integer-Typ. Wenn wir das so beibehalten, wird uns das Leben später leichter fallen. Gleichzeitig wird durch die Verwendung eines uchar für den Parameter die Anzahl der Unterfenster bequem auf 255 begrenzt. Dieser Ansatz gewährleistet sowohl Kompatibilität als auch Flexibilität.

Wenn wir zu Zeile 149 gehen, sehen wir, wie dieser Wert verwendet wird. Es informiert MetaTrader 5, in welchem Chartfenster das Objekt angezeigt werden soll. Für diejenigen, die mit MQL5 vertraut sind, ist dies eine gängige Technik. Interessanter wird es jedoch, wenn wir die Funktion IndicadorCheckPass untersuchen, die in Zeile 159 beginnt.

Wenn wir einen Indikator in einem Chart platzieren, müssen wir normalerweise nicht angeben, in welchem Fenster er dargestellt wird. Beim Arbeiten mit grafischen Objekten wird es jedoch komplexer, da wir explizit bestimmen müssen, welches Fenster das Objekt enthalten soll. Ohne diese Information würde der Funktionsaufruf in Zeile 149 das Objekt immer im falschen Fenster platzieren. Wie bestimmen wir also das richtige Fenster?

Um dies effizient zu tun, verwenden wir in Zeile 164 einen einfachen Trick: Wir weisen dem Indikator einen temporären Namen zu. Dann verwenden wir in Zeile 165 die Funktion ChartWindowFind in MQL5. Mit dieser Funktion kann MetaTrader 5 uns genau mitteilen, welches Fenster den Indikator enthält. Wichtig dabei ist, dass kein anderer Indikator einen ähnlichen temporären Namen haben sollte, da dies zu Fehlalarmen führen könnte. Wenn ChartWindowFind keinen gültigen Fensterindex zurückgibt, wird standardmäßig das Hauptfenster (Index 0) verwendet. Schließlich stellen wir in Zeile 166 sicher, dass im Chart nur eine einzige Instanz des Indikators vorhanden ist. Die restliche Funktion bleibt unverändert und bedarf keiner weiteren Erläuterung.

Mit diesen Anpassungen können wir das Mausanzeigemodul nun in jedem beliebigen Unterfenster positionieren. Damit es jedoch richtig funktioniert, müssen wir einige Aspekte des Klassencodes ändern. Als Nächstes werden wir kurz die an der Header-Datei C_Mouse.mqh vorgenommenen Änderungen überprüfen. Sie können es unten sehen.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_"
007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0))
008. //+------------------------------------------------------------------+
009. class C_Mouse : public C_Terminal
010. {
011.    public   :
012.       enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
013.       enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
014.       struct st_Mouse
015.       {
016.          struct st00
017.          {
018.             short    X_Adjusted,
019.                      Y_Adjusted,
020.                      X_Graphics,
021.                      Y_Graphics;
022.             double   Price;
023.             datetime dt;
024.          }Position;
025.          uchar      ButtonStatus;
026.          bool       ExecStudy;
027.       };
028. //+------------------------------------------------------------------+
029.    protected:
030. //+------------------------------------------------------------------+
031.       void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const
032.          {
033.             if (!m_OK) return;
034.             CreateObjectGraphics(szName, OBJ_BUTTON);
035.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
036.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
037.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
038.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
039.             ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
040.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
041.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
042.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
044.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
045.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
046.          }
047. //+------------------------------------------------------------------+
048.    private   :
049.       enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
050.       struct st01
051.       {
052.          st_Mouse Data;
053.          color    corLineH,
054.                   corTrendP,
055.                   corTrendN;
056.          eStudy   Study;
057.       }m_Info;
058.       struct st_Mem
059.       {
060.          bool     CrossHair,
061.                   IsFull;
062.          datetime dt;
063.          string   szShortName,
064.                   szLineH,
065.                   szLineV,
066.                   szLineT,
067.                   szBtnS;
068.       }m_Mem;
069.       bool m_OK;
070. //+------------------------------------------------------------------+
071.       void GetDimensionText(const string szArg, int &w, int &h)
072.          {
073.             TextSetFont("Lucida Console", -100, FW_NORMAL);
074.             TextGetSize(szArg, w, h);
075.             h += 5;
076.             w += 5;
077.          }
078. //+------------------------------------------------------------------+
079.       void CreateStudy(void)
080.          {
081.             if (m_Mem.IsFull)
082.             {
083.                CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH);
084.                CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH);
085.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2);
086.                CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy);
087.             }
088.             m_Info.Study = eStudyCreate;
089.          }
090. //+------------------------------------------------------------------+
091.       void ExecuteStudy(const double memPrice)
092.          {
093.             double v1 = GetInfoMouse().Position.Price - memPrice;
094.             int w, h;
095.             
096.             if (!CheckClick(eClickLeft))
097.             {
098.                m_Info.Study = eStudyNull;
099.                ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
100.                if (m_Mem.IsFull)   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
101.             }else if (m_Mem.IsFull)
102.             {
103.                string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
104.                   MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0));
105.                GetDimensionText(sz1, w, h);
106.                ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1);                                                
107.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
108.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w);
109.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h);
110.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
111.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));            
112.                ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
113.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
114.             }
115.             m_Info.Data.ButtonStatus = eKeyNull;
116.          }
117. //+------------------------------------------------------------------+
118. inline void DecodeAlls(int xi, int yi)
119.          {
120.             int w = 0;
121. 
122.             xi = (xi > 0 ? xi : 0);
123.             yi = (yi > 0 ? yi : 0);
124.             ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
125.             m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
126.             m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price);
127.             ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi);
128.             yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin);
129.             m_Info.Data.Position.X_Adjusted = (short) xi;
130.             m_Info.Data.Position.Y_Adjusted = (short) yi;
131.          }
132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Mouse(const long id, const string szShortName)
136.          :C_Terminal(id),
137.          m_OK(false)
138.          {
139.             m_Mem.szShortName = szShortName;
140.          }
141. //+------------------------------------------------------------------+
142.       C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
143.          :C_Terminal(id)
144.          {
145.             if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError != ERR_SUCCESS) return;
147.             m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
148.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
149.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
150.             ZeroMemory(m_Info);
151.             m_Info.corLineH  = corH;
152.             m_Info.corTrendP = corP;
153.             m_Info.corTrendN = corN;
154.             m_Info.Study = eStudyNull;
155.             if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
156.                CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH);
157.             ChartRedraw(GetInfoTerminal().ID);
158.          }
159. //+------------------------------------------------------------------+
160.       ~C_Mouse()
161.          {
162.             if (!m_OK) return;
163.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
164.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1);
165.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
166.             ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
167.          }
168. //+------------------------------------------------------------------+
169. inline bool CheckClick(const eBtnMouse value) 
170.          {
171.             return (GetInfoMouse().ButtonStatus & value) == value;
172.          }
173. //+------------------------------------------------------------------+
174. inline const st_Mouse GetInfoMouse(void)
175.          {
176.             if (!m_OK)
177.             {
178.                double Buff[];
179.                uCast_Double loc;
180.                int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
181. 
182.                ZeroMemory(m_Info.Data);
183.                if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
184.                {
185.                   loc.dValue = Buff[0];
186.                   m_Info.Data.ButtonStatus = loc._8b[0];
187.                   DecodeAlls((int)loc._16b[1], (int)loc._16b[2]);
188.                }
189.                IndicatorRelease(handle);
190.             }
191. 
192.             return m_Info.Data;
193.          }
194. //+------------------------------------------------------------------+
195. inline void SetBuffer(const int rates_total, double &Buff[])
196.          {
197.             uCast_Double info;
198.             
199.             info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0);
200.             info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics;
201.             info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics;
202.             Buff[rates_total - 1] = info.dValue;
203.          }
204. //+------------------------------------------------------------------+
205.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
206.          {
207.             int w = 0;
208.             static double memPrice = 0;
209.       
210.             if (m_OK)      
211.             {
212.                C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
213.                switch (id)
214.                {
215.                   case (CHARTEVENT_CUSTOM + evHideMouse):
216.                      if (m_Mem.IsFull)   ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE);
217.                      break;
218.                   case (CHARTEVENT_CUSTOM + evShowMouse):
219.                      if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH);
220.                      break;
221.                   case CHARTEVENT_MOUSE_MOVE:
222.                      DecodeAlls((int)lparam, (int)dparam);
223.                      if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price);
224.                      if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0);
225.                      m_Info.Data.ButtonStatus = (uchar) sparam;
226.                      if (CheckClick(eClickMiddle))
227.                         if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
228.                      if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
229.                      {
230.                         ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
231.                         if (m_Mem.IsFull)   ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
232.                         m_Info.Study = eStudyExecute;
233.                      }
234.                      if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
235.                      m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
236.                      break;
237.                   case CHARTEVENT_OBJECT_DELETE:
238.                      if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH))
239.                         CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH);
240.                      break;
241.                }
242.             }
243.          }
244. //+------------------------------------------------------------------+
245. };
246. //+------------------------------------------------------------------+
247. #undef macro_NameObjectStudy
248. //+------------------------------------------------------------------+

Quellcode der Header-Datei C_Mouse.mqh

Das erste, was sofort unsere Aufmerksamkeit erregt, ist in den Zeilen 6 und 7 zu sehen. Beachten Sie, dass wir im Gegensatz zu zuvor eine etwas andere Namenskonvention implementiert haben. Diese Anpassung ist notwendig, um Konflikte mit vorhandenen Objektnamen im Chart zu vermeiden. Dadurch ist der Name nun sowohl vom Fenster als auch von der Anzahl der im Chart vorhandenen Objekte abhängig. Mit anderen Worten: Jeder Name ist jetzt einzigartig.

Es gibt einige geringfügige Unterschiede im Code, aber nichts, was wirklich größere Aufmerksamkeit verdient. Zwischen den Zeilen 64 und 67 werden jedoch neue Variablen deklariert. Diese Variablen werden verwendet, um die Namen der zu erstellenden Objekte zu speichern. Um zu verstehen, wie das funktioniert, schauen Sie sich Zeile 83 an, wo ein Beispiel für die Benennung eines der Objekte bereitgestellt wird. Hier wird deutlich, wie das Makro zum Generieren und Zuweisen eines Namens zu einer der Variablen verwendet wird.

Obwohl ein großer Teil des Codes keiner besonderen Hervorhebung bedarf, da die Änderungen subtil waren und die Unterstützung für unsere Anforderungen verbessern sollten, gibt es einen Abschnitt, der einige Erklärungen erfordert. Es ist zwar nicht perfekt, reicht aber aus, um unsere Ziele zu erreichen. Ich beziehe mich auf die Prozedur, die in Zeile 118, DecodeAlls, beginnt.

Dieses Verfahren funktioniert wunderbar, wenn sich das Mausanzeigemodul im Hauptfenster befindet (also im Fenster mit dem Index Null). Wenn wir den Mauszeiger jedoch in ein anderes Fenster platzieren, beginnen Probleme aufzutreten. Obwohl wir viele davon gelöst haben, bleiben einige bestehen, wie Sie im Video am Ende dieses Artikels sehen werden.

Der entscheidende Punkt, der Sie, lieber Leser, völlig verwirrt und desorientiert zurücklassen könnte, liegt in Zeile 128. Warum existiert diese Linie und warum war sie vorher nicht da? Um dies zu verstehen, ist es wichtig, etwas anderes zu verstehen. Das Mausanzeigemodul sollte ursprünglich nur im Hauptfenster angezeigt werden. Die anfängliche Y-Position dieses Fensters befindet sich oben, was bedeutet, dass Y immer bei Null beginnt. Wenn wir jedoch zusätzliche Fenster hinzufügen, bleibt die Y-Position des Hauptfensters unverändert, während die der zusätzlichen Fenster um einen bestimmten Wert verschoben werden. Für das Betriebssystem (Windows) spielt dies jedoch keine Rolle und informiert MetaTrader 5 über die genaue Position der Maus.

MetaTrader 5 passt dann die Mausposition so an, dass sie innerhalb des Chartfensters bleibt. Folglich können Werte außerhalb des Fensters entweder negativ oder positiv sein. Ein negativer Wert tritt auf, wenn sich der Mauszeiger über dem Arbeitsbereich des Fensters befindet. Wenn Sie nicht wissen, was den Arbeitsbereich des Fensters ausmacht, sehen Sie sich Abbildung 01 an, wo der gesamte Bereich, der die Bitmap enthält, als Arbeitsbereich betrachtet wird.

 Abbildung 01

Abbildung 01 - Verständnis des Arbeitsbereichs

Beachten Sie, dass die Ränder und die Titelleiste nicht zum Arbeitsbereich des Fensters gehören. Wenn der Mauszeiger in den Bereich der Titelleiste gelangt, ist es daher MetaTrader 5 und nicht das Betriebssystem, das den Wert korrigiert und ihn negativ macht. Es ist wichtig zu verstehen, dass der Y-Wert negativ wird, wenn der Zeiger den Bereich der Titelleiste betritt, und zwar nicht, weil das Betriebssystem dies getan hat, sondern weil MetaTrader 5 den Wert korrigiert hat, um ihn innerhalb des Arbeitsbereichs zu halten.

Wenn Sie im Hauptfenster ein Element hinzufügen, das einen Bereich erstellt, z. B. einen Streifen, in den das Chart nicht mehr gehört, erkennt MetaTrader 5 dies nicht als separates Fenster. Auch wenn wir es so definieren, betrachtet MetaTrader 5 das gesamte Fenster weiterhin als eine Einheit und der gesamte Arbeitsbereich bleibt innerhalb der Grenzen des Hauptfensters. Glücklicherweise können wir mit MetaTrader 5 erkennen, wo die Region beginnt, allerdings erhalten wir keine Informationen darüber, wo sie endet. Es liegt an uns, einen Weg zu finden, dies festzustellen. Dennoch ist es schon von Vorteil, zu wissen, wo die Region beginnt.

Um zu ermitteln, wo die Region beginnt, rufen wir ChartGetInteger auf und übergeben die Konstante CHART_WINDOW_YDISTANCE und die Unterfensternummer. Obwohl wir der Einfachheit halber den Begriff „Unterfenster“ verwenden, ist diese Terminologie nicht ganz korrekt.

Der vom Aufruf in Zeile 128 zurückgegebene Wert wird vom konvertierten Wert abgezogen. Denken Sie daran, dass der konvertierte Wert das darstellt, was MetaTrader 5 meldet, also bleibt er innerhalb des Fensters. Ohne diese Korrektur in Zeile 128 hätten wir eine falsche Anzeige der Position der horizontalen Linie, wenn der Mauszeiger eine Position einnimmt, die MetaTrader 5 als Teil des Unterfensterbereichs interpretiert. Mit dieser Korrektur wird dieses Problem jedoch vermieden. Zumindest nicht auf die Art und Weise, wie es normalerweise geschehen wäre.

Ein weiterer wichtiger Punkt, der Erwähnung verdient, findet sich in Zeile 145, wo wir den Kurznamen des Indikators speichern. Aber warum machen wir das? Der Grund wird in Zeile 164 deutlich. Ohne den Namen des Mausindikators zu kennen, könnten wir nicht prüfen und feststellen, ob wir MetaTrader 5 anweisen können, das Senden von Mausereignissen zu beenden. Einige fragen sich vielleicht, ob wir das Ereignis einfach immer aktiviert lassen könnten, aber das wäre unnötig. Bei jeder Mausbewegung löst MetaTrader 5 ein Mausereignis aus. Wenn es niemand verwendet, wird der Ereigniswarteschlange lediglich etwas Nutzloses hinzugefügt. Um dies zu vermeiden, deaktivieren wir das Ereignis, sobald wir es nicht mehr benötigen. Aber wie könnten wir ohne Kenntnis des Namens des Mausindikators feststellen, ob es einen Indikator gibt, der das Ereignis noch benötigt? Das wäre unmöglich. Der Einfachheit halber speichern wir den Indikatornamen und prüfen damit, ob wir das Mausereignis ausschalten können oder nicht.

Neben den oben genannten Änderungen gibt es noch weitere, da diese jedoch relativ einfacher sind, werde ich nicht näher darauf eingehen. Um sie zu finden, vergleichen Sie einfach diesen Code mit den vorherigen Versionen der Header-Datei C_Mouse.mqh. Dies wird Ihre Hausaufgabe sein, mehr darüber zu lernen, wie Sie Dinge codieren und ändern, ohne größere Probleme in den endgültigen Code einzuführen.

Wie erwartet wurden auch in der Datei C_Study.mqh einige Änderungen vorgenommen. Allerdings werde ich hier, ebenso wie bei den Änderungen in der Header-Datei C_Mouse.mqh, nicht näher darauf eingehen. Stattdessen werde ich den vollständigen Code von C_Study.mqh bereitstellen, damit Sie sehen können, wie er geändert wurde.

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.       const datetime GetBarTime(void)
028.          {
029.             datetime dt;
030.             int i0 = PeriodSeconds();
031.             
032.             if (m_Info.Status == eInReplay)
033.             {
034.                if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX;
035.             }else dt = TimeCurrent();
036.             if (m_Info.Rate.time <= dt)
037.                m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0;
038. 
039.             return m_Info.Rate.time - dt;
040.          }
041. //+------------------------------------------------------------------+
042.       void Draw(void)
043.          {
044.             double v1;
045.             
046.             if (m_Info.bvT)
047.             {
048.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
049.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
050.             }
051.             if (m_Info.bvD)
052.             {
053.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
054.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
055.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
056.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
057.             }
058.             if (m_Info.bvP)
059.             {
060.                v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
061.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
062.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
063.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
064.             }
065.          }
066. //+------------------------------------------------------------------+
067. inline void CreateObjInfo(EnumEvents arg)
068.          {
069.             switch (arg)
070.             {
071.                case evShowBarTime:
072.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
073.                   m_Info.bvT = true;
074.                   break;
075.                case evShowDailyVar:
076.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
077.                   m_Info.bvD = true;
078.                   break;
079.                case evShowPriceVar:
080.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
081.                   m_Info.bvP = true;
082.                   break;
083.             }
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveObjInfo(EnumEvents arg)
087.          {
088.             string sz;
089.             
090.             switch (arg)
091.             {
092.                case evHideBarTime:
093.                   sz = m_Info.szBtn1;
094.                   m_Info.bvT = false;
095.                   break;
096.                case evHideDailyVar:
097.                   sz = m_Info.szBtn2;
098.                   m_Info.bvD   = false;
099.                   break;
100.                case evHidePriceVar:
101.                   sz = m_Info.szBtn3;
102.                   m_Info.bvP = false;
103.                   break;
104.             }
105.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
106.             ObjectDelete(GetInfoTerminal().ID, sz);
107.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
108.          }
109. //+------------------------------------------------------------------+
110.    public   :
111. //+------------------------------------------------------------------+
112.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
113.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
114.          {
115.             if (_LastError != ERR_SUCCESS) return;
116.             ZeroMemory(m_Info);
117.             m_Info.Status = eCloseMarket;
118.             m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
119.             m_Info.corP = corP;
120.             m_Info.corN = corN;
121.             CreateObjInfo(evShowBarTime);
122.             CreateObjInfo(evShowDailyVar);
123.             CreateObjInfo(evShowPriceVar);
124.          }
125. //+------------------------------------------------------------------+
126.       void Update(const eStatusMarket arg)
127.          {
128.             datetime dt;
129.             
130.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
131.             {
132.                case eCloseMarket   :
133.                   m_Info.szInfo = "Closed Market";
134.                   break;
135.                case eInReplay      :
136.                case eInTrading   :
137.                   if ((dt = GetBarTime()) < ULONG_MAX)
138.                   {
139.                      m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
140.                      break;
141.                   }
142.                case eAuction      :
143.                   m_Info.szInfo = "Auction";
144.                   break;
145.                default            :
146.                   m_Info.szInfo = "ERROR";
147.             }
148.             Draw();
149.          }
150. //+------------------------------------------------------------------+
151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
152.          {
153.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
154.             switch (id)
155.             {
156.                case CHARTEVENT_CUSTOM + evHideBarTime:
157.                   RemoveObjInfo(evHideBarTime);
158.                   break;
159.                case CHARTEVENT_CUSTOM + evShowBarTime:
160.                   CreateObjInfo(evShowBarTime);
161.                   break;
162.                case CHARTEVENT_CUSTOM + evHideDailyVar:
163.                   RemoveObjInfo(evHideDailyVar);
164.                   break;
165.                case CHARTEVENT_CUSTOM + evShowDailyVar:
166.                   CreateObjInfo(evShowDailyVar);
167.                   break;
168.                case CHARTEVENT_CUSTOM + evHidePriceVar:
169.                   RemoveObjInfo(evHidePriceVar);
170.                   break;
171.                case CHARTEVENT_CUSTOM + evShowPriceVar:
172.                   CreateObjInfo(evShowPriceVar);
173.                   break;
174.                case (CHARTEVENT_CUSTOM + evSetServerTime):
175.                   m_Info.TimeDevice = (datetime)dparam;
176.                   break;
177.                case CHARTEVENT_MOUSE_MOVE:
178.                   Draw();
179.                   break;
180.             }
181.             ChartRedraw(GetInfoTerminal().ID);
182.          }
183. //+------------------------------------------------------------------+
184. };
185. //+------------------------------------------------------------------+
186. #undef def_ExpansionPrefix
187. #undef def_MousePrefixName
188. //+------------------------------------------------------------------+

Quellcode der Klasse C_Study.mqh

In diesem Artikel möchte ich jedoch den folgenden Code zeigen. Dies ist der Quellcode des Indikators.

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.59"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/pt/articles/12075"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. //+------------------------------------------------------------------+
13. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
14. //+------------------------------------------------------------------+
15. C_Study *Study       = NULL;
16. //+------------------------------------------------------------------+
17. input color user02   = clrBlack;                       //Price Line
18. input color user03   = clrPaleGreen;                   //Positive Study
19. input color user04   = clrLightCoral;                  //Negative Study
20. //+------------------------------------------------------------------+
21. C_Study::eStatusMarket m_Status;
22. int m_posBuff = 0;
23. double m_Buff[];
24. //+------------------------------------------------------------------+
25. int OnInit()
26. {
27.    ResetLastError();
28.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
29.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
30.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
31.    {
32.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
33.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
34.       m_Status = C_Study::eCloseMarket;
35.    }else
36.       m_Status = C_Study::eInReplay;
37.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
38.    ArrayInitialize(m_Buff, EMPTY_VALUE);
39.    
40.    return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
44. {
45.    m_posBuff = rates_total;
46.    (*Study).Update(m_Status);   
47.    
48.    return rates_total;
49. }
50. //+------------------------------------------------------------------+
51. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
52. {
53.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
54.    (*Study).SetBuffer(m_posBuff, m_Buff);
55.    
56.    ChartRedraw((*Study).GetInfoTerminal().ID);
57. }
58. //+------------------------------------------------------------------+
59. void OnBookEvent(const string &symbol)
60. {
61.    MqlBookInfo book[];
62.    C_Study::eStatusMarket loc = m_Status;
63.    
64.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
65.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
66.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
67.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
68.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
69.    if (loc != m_Status) (*Study).Update(m_Status);
70. }
71. //+------------------------------------------------------------------+
72. void OnDeinit(const int reason)
73. {
74.    if (reason != REASON_INITFAILED)
75.    {
76.       if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
77.          MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
78.    }
79.    delete Study;
80. }
81. //+------------------------------------------------------------------+
82. 

Der Quellcode des Mauszeigers

In diesem Code fällt gleich zu Beginn auf, dass wir bestimmte Eingaben nicht mehr verwenden. Dies bedeutet, dass unsere Herangehensweise an diesen Indikator geändert wurde. Der Grund für diese Änderung besteht darin, Ihnen die Freiheit zu geben, den Indikator dort zu platzieren, wo Sie es wünschen. Es könnte für Zwecke verwendet werden, die über meine ursprünglichen Vorstellungen hinausgehen. Deshalb werde ich hierfür keinen konkreten Ort mehr angeben. Folglich sind die Eingaben, bei denen der Nutzer zuvor die Chart-ID und den Asset-Status festlegen musste, nicht mehr erforderlich.

Ich möchte Sie jedoch auf eine kleine Änderung in Zeile 36 aufmerksam machen. Wenn dieses Modul nun zum Chart des vom Wiedergabe-/Simulatordienst verwendeten Assets hinzugefügt wird, werden in Zeile 36 automatisch Anpassungen vorgenommen. Damit entfällt für den Nutzer die Möglichkeit, bisher notwendige Änderungen, wie beispielsweise die Angabe der Chart-ID, vorzunehmen. Eine Anmerkung: Obwohl die Chart-ID nicht mehr angegeben werden muss, gilt alles, was in den vorherigen Artikeln bezüglich der Funktionsaufrufe besprochen wurde, weiterhin, zumindest bis zu dem Zeitpunkt, an dem ich diesen Artikel schreibe.


Schlussfolgerung

Mit ein wenig Arbeit und unter Nutzung meines Vorwissens konnte ich demonstrieren, wie bestimmte Funktionen in MetaTrader 5 funktionieren. Es stimmt, wenn Sie die Artikel verfolgt haben, könnten Sie den Eindruck haben, dass wir keine großen Fortschritte gemacht haben. Tatsächlich haben wir Fortschritte gemacht, wenn auch langsamer. Dies liegt daran, dass ich mir Dinge ausdenken und testen musste, bei denen ich nicht sicher war, ob sie in MetaTrader 5 tatsächlich möglich waren.

Viele Leute, die sich Programmierer nennen, behaupten einfach, dass bestimmte Dinge in MetaTrader 5 nicht möglich seien – dass der Plattform diese oder jene Funktion fehle. Ich habe jedoch festgestellt, dass diese Personen häufig falsch informiert sind.

Zum Abschluss dieses Artikels zeige ich Ihnen noch ein Video, das die Funktionsweise des Mausanzeigemoduls zeigt. Schauen Sie sich das Video genau an und beachten Sie, dass es einen Fehler gibt. Ich bin mir dessen bewusst, aber da es nicht kritisch ist, werde ich die Fehlerbehebung für ein anderes Mal aufheben, wenn ich ein besseres Verständnis davon habe, wie MetaTrader 5 wirklich funktioniert, insbesondere in den Aspekten, die ich noch nicht vollständig verstanden habe.

Ich möchte, dass Sie, lieber Leser, Teil dieses Lernprozesses sind. Ich werde also weiterhin zeigen, wie das für die Wiedergabe/Simulation konzipierte System auch für den Einsatz auf dem realen Markt und auf Demokonten entwickelt wird.


Demonstrationsvideo: Der neue Mausindikator im Einsatz

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

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Meistern Sie MQL5 vom Anfänger bis zum Profi (Teil IV): Über Arrays, Funktionen und globale Terminalvariablen Meistern Sie MQL5 vom Anfänger bis zum Profi (Teil IV): Über Arrays, Funktionen und globale Terminalvariablen
Der Artikel ist eine Fortsetzung der Serie für Einsteiger. Ers behandelt im Detail Datenarrays, die Interaktion von Daten und Funktionen sowie globale Terminalvariablen, die einen Datenaustausch zwischen verschiedenen MQL5-Programmen ermöglichen.
Neuronale Netze im Handel: Verwenden von Sprachmodellen für die Zeitreihenprognose Neuronale Netze im Handel: Verwenden von Sprachmodellen für die Zeitreihenprognose
Wir untersuchen weiterhin Modelle zur Zeitreihenprognose. In diesem Artikel machen wir uns mit einem komplexen Algorithmus vertraut, der auf der Verwendung eines vortrainierten Sprachmodells basiert.
Dekonstruktion von Beispielen für Handelsstrategien im Client-Terminal Dekonstruktion von Beispielen für Handelsstrategien im Client-Terminal
Der Artikel verwendet Blockdiagramme, um die Logik der auf Kerzen basierenden Trainings-EAs zu untersuchen, die sich im Ordner Experts\Free Robots des Terminals befinden.
Von der Grundstufe bis zur Mittelstufe: Variablen (III) Von der Grundstufe bis zur Mittelstufe: Variablen (III)
Heute schauen wir uns an, wie vordefinierte Variablen und Konstanten der Sprache MQL5 verwendet werden. Darüber hinaus werden wir einen weiteren speziellen Variablentyp analysieren: Funktionen. Zu wissen, wie man richtig mit diesen Variablen arbeitet, kann den Unterschied zwischen einer funktionierenden und einer nicht funktionierenden Anwendung ausmachen. Um zu verstehen, was hier vorgestellt wird, ist es notwendig, das Material zu verstehen, das in früheren Artikeln besprochen wurde.