English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems (Teil 61): Den Dienst abspielen (II)

Entwicklung eines Replay-Systems (Teil 61): Den Dienst abspielen (II)

MetaTrader 5Beispiele | 30 April 2025, 09:13
15 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „ Entwicklung eines Replay-Systems (Teil 60): Wiedergabe des Dienstes (I)“ haben wir einige Anpassungen vorgenommen, damit der Wiedergabe-/Simulationsdienst mit der Erzeugung neuer Daten auf dem Chart beginnen kann. Obwohl wir nur minimale Änderungen vornahmen, damit das System mit dem Abruf von Daten beginnen konnte, wurde schnell klar, dass etwas Ungewöhnliches passiert war. Obwohl keine größeren Änderungen vorgenommen wurden, scheint das System einen erheblichen Rückschlag erlitten zu haben. Diese Situation erweckt den Eindruck, dass das System nicht mehr lebensfähig ist, da es sich plötzlich drastisch verlangsamt hat. Ist das wirklich so? Und wenn ja, wie können wir dieses Problem lösen? Es ist wichtig, daran zu denken, dass wir versuchen, alles an den Prinzipien der objektorientierten Programmierung auszurichten.

Obwohl es tatsächlich zu einem Leistungsabfall kam, können wir dieses Problem größtenteils beheben, indem wir bestimmte Aspekte des Codes verstehen und entsprechend anpassen. In diesem Artikel zeige ich Ihnen, wie Sie einige der in MetaEditor verfügbaren Werkzeuge nutzen können, die die Verfeinerung und Verbesserung des Codes erheblich erleichtern. Im Nachhinein betrachtet, hätte ich dieses Thema schon vor einigen Artikeln einführen sollen. Ich sah jedoch nicht die gleiche Notwendigkeit wie jetzt, wo es entscheidend ist, zu verstehen, wie der Code funktioniert und warum seine Leistung so stark nachgelassen hat.


Umsetzung der deutlichsten und direktesten Verbesserungen

Missverständnisse oder ein Mangel an ausführlichen Erklärungen über die Funktionsweise von MetaTrader 5 und MQL5 sind oft ein großes Hindernis bei bestimmten Implementierungen. Glücklicherweise können wir innerhalb der Gemeinschaft unser Wissen konsolidieren und effektiv austauschen, auch wenn es keine unmittelbare Lösung für unsere aktuellen Umsetzungsprobleme bietet. Unabhängig davon ist es immer von Vorteil, über genaues und hochwertiges Wissen zu verfügen.

Einer dieser Schlüsselaspekte ist genau das, was ich versuchen werde zu erklären. Vieles von dem, was ich erörtere, ist leichter zu verstehen, wenn Sie MQL5 aktiv nutzen. So können Sie mit MetaTrader 5 weit mehr erreichen, als die meisten Entwickler normalerweise erreichen oder versuchen.

Einer der vielleicht am meisten missverstandenen Aspekte für viele MQL5-Programmierer sind grafische Objekte. Viele glauben, dass auf diese Objekte nur über etwas zugegriffen werden kann, das sich direkt im Chart befindet: ein Indikator, ein Skript oder sogar ein Expert Advisor. Dies ist jedoch weit von der Wahrheit entfernt.

Bis jetzt haben wir so gearbeitet, dass keine Abhängigkeiten zwischen dem, was im nutzerdefinierten Chart-Fenster des Finanzinstruments angezeigt wird, und dem, was in MetaTrader 5 ausgeführt wird, entstehen. Neben den Methoden, die wir derzeit für die Übertragung von Informationen zwischen den in MetaTrader 5 laufenden Anwendungen verwenden, gibt es jedoch auch die Möglichkeit, einen ausgefeilteren (aber auch riskanteren) Ansatz zu implementieren. Verstehen Sie mich nicht falsch: Wenn Abhängigkeiten zwischen dem, was ausgeführt wird, und dem, was wir erwarten, eingeführt werden, können unerwartete Probleme auftreten.

Auch wenn dieser Ansatz in vielen Fällen funktioniert, kann er uns auf einen komplexen und problematischen Weg führen, der möglicherweise zu Zeitverschwendung führt, die an anderer Stelle besser investiert werden könnte. Der Grund dafür ist, dass solche Änderungen es oft unmöglich machen, weitere Verbesserungen vorzunehmen oder neue Funktionen zu implementieren. Um zu verstehen, was ich vorschlage, muss man wissen, wie das System als Ganzes funktioniert.

Der erste kritische Punkt, den Sie beachten sollten, ist, dass das Kontrollindikatormodul nur dann auf dem Chart zu sehen ist, wenn der Wiedergabe-/Simulationsdienst läuft. Sie sollten nicht versuchen, das Steuermodul manuell zum Chart hinzuzufügen, da dies alles, was wir jetzt implementieren werden, stören würde.

Der zweite wichtige Punkt ist, dass alle vom Steuermodul erstellten grafischen Objekte einer strikten und konsistenten Namenskonvention folgen müssen; andernfalls werden wir später ernsthafte Probleme bekommen.

Zusätzlich zu diesen beiden Punkten werden wir auch Änderungen vornehmen, die die Lesbarkeit des Codes erheblich verbessern. Es ist unbedingt zu vermeiden, dass Symbole oder Markierungen verwendet werden, die keine klare Bedeutung haben. Diese Verbesserungen der Lesbarkeit dienen jedoch in erster Linie dazu, bestimmte Anpassungen leichter verständlich zu machen, und nicht dazu, die Ausführungsgeschwindigkeit des Codes zu erhöhen. Dies wird deutlicher, wenn wir uns den Quellcode ansehen.

Die ersten Änderungen, die wir vornehmen werden, betreffen die Header-Datei C_Controls.mqh. Bevor wir uns jedoch damit befassen, warum diese Änderungen notwendig sind, sollten wir uns zunächst die Änderungen ansehen, die an dieser Datei vorgenommen wurden. Der neue Code ist unten abgebildet:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft        def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock   def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight       def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock  def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
024. #define def_PosXObjects         120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons         32
027. #define def_ColorFilter         0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Terminal.mqh"
030. #include "..\Auxiliar\C_Mouse.mqh"
031. //+------------------------------------------------------------------+
032. class C_Controls : private C_Terminal
033. {
034.    protected:
035.    private   :
036. //+------------------------------------------------------------------+
037.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
038.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
039. //+------------------------------------------------------------------+
040.       struct st_00
041.       {
042.          string  szBarSlider,
043.                  szBarSliderBlock;
044.          ushort  Minimal;
045.       }m_Slider;
046.       struct st_01
047.       {
048.          C_DrawImage *Btn;
049.          bool        state;
050.          short       x, y, w, h;
051.       }m_Section[eObjectControl::eNull];
052.       C_Mouse   *m_MousePtr;
053. //+------------------------------------------------------------------+
054. inline void CreteBarSlider(short x, short size)
055.          {
056.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
060.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
065.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
066.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
067.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
068.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
069.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
070.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
071.          }
072. //+------------------------------------------------------------------+
073.       void SetPlay(bool state)
074.          {
075.             if (m_Section[ePlay].Btn == NULL)
076.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
077.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 1 : 0));
078.             if (!state) CreateCtrlSlider();
079.          }
080. //+------------------------------------------------------------------+
081.       void CreateCtrlSlider(void)
082.          {
083.             CreteBarSlider(77, 436);
084.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.             PositionPinSlider(m_Slider.Minimal);
088.          }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.          {         
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.             {
095.                delete m_Section[c0].Btn;
096.                m_Section[c0].Btn = NULL;
097.             }
098.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
099.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.          }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(ushort p)
103.          {
104.             int iL, iR;
105.             
106.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.             m_Section[ePin].x += def_PosXObjects;
110.             m_Section[ePin].x += 95 - (def_SizeButtons / 2);
111.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
113.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
114.          }
115. //+------------------------------------------------------------------+
116. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
117.          {
118.             C_Mouse::st_Mouse InfoMouse;
119.             
120.             InfoMouse = (*m_MousePtr).GetInfoMouse();
121.             x = (short) InfoMouse.Position.X_Graphics;
122.             y = (short) InfoMouse.Position.Y_Graphics;
123.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
124.             {   
125.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
126.                   return c0;
127.             }
128.             
129.             return eNull;
130.          }
131. //+------------------------------------------------------------------+
132.    public   :
133. //+------------------------------------------------------------------+
134.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
135.          :C_Terminal(Arg0),
136.           m_MousePtr(MousePtr)
137.          {
138.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
139.             if (_LastError != ERR_SUCCESS) return;
140.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
141.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
142.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
143.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
144.             {
145.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
146.                m_Section[c0].y = 25;
147.                m_Section[c0].Btn = NULL;
148.             }
149.             m_Section[ePlay].x = def_PosXObjects;
150.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
151.             m_Section[eRight].x = m_Section[ePlay].x + 511;
152.             m_Slider.Minimal = eTriState;
153.          }
154. //+------------------------------------------------------------------+
155.       ~C_Controls()
156.          {
157.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
158.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
159.             delete m_MousePtr;
160.          }
161. //+------------------------------------------------------------------+
162.       void SetBuffer(const int rates_total, double &Buff[])
163.          {
164.             uCast_Double info;
165.             
166.             info._16b[eCtrlPosition] = m_Slider.Minimal;
167.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));//SHORT_MAX : SHORT_MIN);
168.             if (rates_total > 0)
169.                Buff[rates_total - 1] = info.dValue;
170.          }
171. //+------------------------------------------------------------------+
172.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
173.          {
174.             short x, y;
175.             static ushort iPinPosX = 0;
176.             static short six = -1, sps;
177.             uCast_Double info;
178.             
179.             switch (id)
180.             {
181.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
182.                   info.dValue = dparam;
183.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
184.                   x = (short) info._16b[eCtrlPosition];
185.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
186.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
187.                   break;
188.                case CHARTEVENT_OBJECT_DELETE:
189.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
190.                   {
191.                      if (sparam == def_ObjectCtrlName(ePlay))
192.                      {
193.                         delete m_Section[ePlay].Btn;
194.                         m_Section[ePlay].Btn = NULL;
195.                         SetPlay(m_Section[ePlay].state);
196.                      }else
197.                      {
198.                         RemoveCtrlSlider();
199.                         CreateCtrlSlider();
200.                      }
201.                   }
202.                   break;
203.                case CHARTEVENT_MOUSE_MOVE:
204.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
205.                   {
206.                      case ePlay:
207.                         SetPlay(!m_Section[ePlay].state);
208.                         if (m_Section[ePlay].state)
209.                         {
210.                            RemoveCtrlSlider();
211.                            m_Slider.Minimal = iPinPosX;
212.                         }else CreateCtrlSlider();
213.                         break;
214.                      case eLeft:
215.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
216.                         break;
217.                      case eRight:
218.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
219.                         break;
220.                      case ePin:
221.                         if (six == -1)
222.                         {
223.                            six = x;
224.                            sps = (short)iPinPosX;
225.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
226.                         }
227.                         iPinPosX = sps + x - six;
228.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
229.                         break;
230.                   }else if (six > 0)
231.                   {
232.                      six = -1;
233.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
234.                   }
235.                   break;
236.             }
237.             ChartRedraw(GetInfoTerminal().ID);
238.          }
239. //+------------------------------------------------------------------+
240. };
241. //+------------------------------------------------------------------+
242. #undef def_PosXObjects
243. #undef def_ButtonPlay
244. #undef def_ButtonPause
245. #undef def_ButtonLeft
246. #undef def_ButtonRight
247. #undef def_ButtonPin
248. #undef def_PathBMP
249. //+------------------------------------------------------------------+

Quellcode der Datei C_Controls.mqh

Die Methode, mit der sichergestellt wird, dass Steuerobjekte einem strengen Format folgen und dennoch einfach zu deklarieren sind, wird in Zeile 23 definiert. Diese Linie mag auf den ersten Blick sehr komplex erscheinen, aber lassen Sie sich nicht von ihrem ungewöhnlichen Aussehen täuschen. Im Zweifelsfall sollten Sie die Funktion isoliert testen, um zu verstehen, warum sie funktioniert.

Achten Sie jetzt auf ein wichtiges Detail. Beachten Sie, dass wir in den Zeilen 37 und 38 zwei Enumerationen haben. Die Enumeration in Zeile 37 existierte vorher nicht, sondern wurde geschaffen, um den Zugriff auf die Daten im Puffer zu vereinfachen. Wie das funktioniert, können Sie anhand der SetBuffer-Prozedur in Zeile 162 sehen. Ein ähnlicher Ansatz wurde für das Verfahren zur Behandlung von Nachrichten angewandt, auch wenn die Umsetzung in diesem Fall etwas anders ist. Überprüfen Sie dies zwischen den Zeilen 182 und 186. Beachten Sie aber besonders die Zeile 184: Sie wurde aus dem ursprünglichen Code entfernt.

Um auf das Thema Enumerationen zurückzukommen, sei darauf hingewiesen, dass die Enumeration in Zeile 38 im Vergleich zu ihrer vorherigen Version geändert wurde. Diese Änderung wurde vorgenommen, um die Lesbarkeit des Codes zu verbessern. So ist beispielsweise die Variable in Zeile 44, die zuvor ein Typ mit Vorzeichen war, nun ein Typ ohne Vorzeichen. Diese Anpassung ermöglicht geringfügige Änderungen, wie sie in Zeile 152 zu sehen sind, oder etwas Ähnliches, wie es in Zeile 186 zu sehen ist.

All diese Verfeinerungen tragen dazu bei, den Code lesbarer zu machen, da das Ziel darin besteht, einen etwas anderen Ansatz als bisher einzuführen.

Lassen Sie uns nun untersuchen, was genau getan werden soll. Diese Änderungen könnten mit der Zeit einige CPU-Zyklen einsparen. Doch zunächst ein wichtiger Aspekt: In Zeile 77 wird eine Änderung des auf dem grafischen Objekt angezeigten Bildes verlangt. Dieses Objekt ist eine Schaltfläche, die anzeigt, ob wir uns im Abspiel- oder im Pausenmodus befinden. Der Dienst überwacht jedoch ständig den Pufferspeicher des Kontrollanzeigers, obwohl wir einen anderen Ansatz wählen können, um festzustellen, ob wir uns im Abspiel- oder im Pausenmodus befinden. Dieser Ansatz bezieht das in Zeile 77 manipulierte Objekt direkt mit ein.


Schneller Zugriff auf den Schaltflächenstatus

Wie im vorangegangenen Abschnitt erwähnt, bieten diese einfachen Änderungen keinen so großen Leistungszuwachs, dass ihre Umsetzung allein gerechtfertigt wäre. Wenn wir jedoch die Bereiche analysieren, in denen der Dienst am meisten Leistungsverbesserungen benötigt, ändert sich die Situation.

Im vorigen Artikel habe ich gezeigt, wo diese Optimierung notwendig ist. Um Ihr Gedächtnis aufzufrischen: Der entscheidende Punkt liegt in LoopEventOnTime. Diese Funktion ruft in regelmäßigen Abständen eine andere Funktion auf, um den Status der Taste des Kontrollindikators zu überprüfen und festzustellen, ob wir uns im Pausen- oder im Wiedergabemodus befinden.

Diese Überprüfung erfolgt zunächst durch Untersuchung der im Puffer des Kontrollindikators gespeicherten Daten. Es gibt jedoch einen etwas eleganteren Ansatz (auch wenn er zusätzliche Komplexität mit sich bringt): die direkte Untersuchung des Kontrollobjekts selbst. Denken Sie daran, dass das Steuerobjekt ein OBJ_BITMAP_LABEL ist, ein Objekttyp mit zwei möglichen Zuständen, die wir überprüfen können, indem wir eine bestimmte Variable darin untersuchen.

Durch die Überprüfung des Wertes einer bestimmten Variablen innerhalb des OBJ_BITMAP_LABEL-Objekts, das auf dem Chart angezeigt wird, können wir den Dienst in die Lage versetzen, das Lesen aus dem Puffer zu umgehen, wenn er feststellt, ob die Datenübertragung zum Chart wiedergegeben oder angehalten werden soll.

Wenn Sie jedoch die Header-Datei C_DrawImage.mqh überprüfen, werden Sie keine Änderungen an der Variablen finden, die wir im Objekt OBJ_BITMAP_LABEL benötigen. Dies ist bereits der Fall, bevor irgendwelche Änderungen am Dienst vorgenommen werden. Wenn Sie jedoch die Datei C_Controls.mqh analysieren, werden Sie feststellen, dass in Zeile 77 eine Anforderung zur Aktualisierung des Objekts gestellt wird. Damit haben wir einen Ausgangspunkt für die Umsetzung der notwendigen Änderungen, damit der Dienst sie nutzen kann. Theoretisch sollte dies zu einer gewissen Einsparung von CPU-Zyklen pro Aufruf führen.

Da diese Änderungen minimal sind, werde ich nicht die gesamte Header-Datei hier einfügen. Öffnen Sie stattdessen die Datei C_DrawImage.mqh und ändern Sie sie wie im folgenden Code-Abschnitt gezeigt:

174. //+------------------------------------------------------------------+
175.       void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
176.          {
177.             
178.             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
179.             ReSizeImage(w, h, cView, what);
180.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
181.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
182.             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
183.             {
184.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName);
185.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName);
186.                ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_STATE, what == 1);
187.                ChartRedraw(GetInfoTerminal().ID);
188.             }
189.          }
190. //+------------------------------------------------------------------+

C_DrawImage.mqh Quellcodeausschnitt

Beachten Sie, dass Zeile 184 durch Zeile 185 ersetzt wurde, weil sie einen Parameter enthält, der den Bildindex angibt. Was uns jedoch wirklich interessiert, ist Zeile 186, die den Zustand der Objektvariablen OBJ_BITMAP_LABEL aktualisiert. Jetzt spiegelt die Variable OBJPROP_STATE direkt den Zustand des Objekts wider. Wir erinnern uns, dass es nur zwei mögliche Zustände gibt: Abspielen oder Pause.

Danach können wir uns den Code in der Header-Datei C_Replay.mqh ansehen, wo der Dienst direkt auf das Objekt zugreifen und feststellen kann, ob wir uns im Abspiel- oder im Pausenmodus befinden.

Damit der Dienst verstehen kann, was vor sich geht, müssen wir zunächst etwas Neues hinzufügen. Die erste Änderung ist gleich unten:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_ConfigService.mqh"
05. #include "C_Controls.mqh"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
08. #resource "\\" + def_IndicatorControl
09. //+------------------------------------------------------------------+
10. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
11. //+------------------------------------------------------------------+
12. #define def_ShortNameIndControl "Market Replay Control"
13. //+------------------------------------------------------------------+
14. class C_Replay : public C_ConfigService
15. {
16.    private   :
17.       struct st00
18.       {
19.          C_Controls::eObjectControl Mode;
20.          uCast_Double               Memory;
21.          ushort                     Position;
22.          int                        Handle;
23.       }m_IndControl;

C_Replay.mqh Quellcode-Ausschnitt

Beachten Sie, dass wir in Zeile 5 einen Verweis auf die Header-Datei des Kontrollindikators hinzugefügt haben. Wir werden nichts implementieren, was die Steuerklasse direkt verwendet, aber wir benötigen Zugriff auf die Definitionen in dieser Datei. Die wichtigste ist diejenige, die es uns ermöglicht, die Namen der von der Klasse erstellten Objekte zu identifizieren. Keine Sorge, dazu kommen wir noch.

Es gibt noch weitere Änderungen in demselben Codeabschnitt. In Zeile 19 zum Beispiel hat die Variable einen anderen Typ, was die Lesbarkeit des Codes verbessert. Außerdem haben wir in Zeile 20 eine neue Variable hinzugefügt. Er wird verwendet, um bestimmte Werte aus dem Puffer des Kontrollindikator zu speichern. Sie wird jedoch nicht genau so genutzt, wie wir es uns wünschen. Dies wird später noch deutlicher werden. Nachdem wir diese Änderungen vorgenommen haben, müssen wir sofort den Konstruktor der Klasse C_Replay korrigieren. Die Änderungen sind unten zu sehen:

131. //+------------------------------------------------------------------+
132.       C_Replay()
133.          :C_ConfigService()
134.          {
135.             Print("************** Market Replay Service **************");
136.             srand(GetTickCount());
137.             SymbolSelect(def_SymbolReplay, false);
138.             CustomSymbolDelete(def_SymbolReplay);
139.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
140.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
141.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
142.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
143.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
144.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
145.             SymbolSelect(def_SymbolReplay, true);
146.             m_Infos.CountReplay = 0;
147.             m_IndControl.Handle = INVALID_HANDLE;
148.             m_IndControl.Mode = C_Controls::ePause;
149.             m_IndControl.Position = 0;
150.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
151.          }
152. //+------------------------------------------------------------------+

C_Replay.mqh Quellcode-Ausschnitt

Beachten Sie, wie die Werte der Struktur m_IndControl initialisiert werden. Es ist wichtig zu verstehen, wie diese Initialisierung durchgeführt wird und vor allem, warum diese bestimmten Werte verwendet werden. Auch wenn die Ursache zu diesem Zeitpunkt noch nicht klar ist, wird sie sich bald herausstellen. Die Idee besteht darin, auf ein Objekt im Chart zuzugreifen, das vom Modul des Kontrollindikators erstellt und gepflegt wird.

Um diese Funktionalität tatsächlich zu nutzen und über den Dienst direkt aus dem Graphen auf das Objekt OBJ_BITMAP_LABEL zuzugreifen, müssen wir den Code von UpdateIndicatorControl, der bereits in der Klasse C_Replay vorhanden ist, leicht abändern. Die Änderung ist in dem folgenden Fragment zu sehen:

34. //+------------------------------------------------------------------+
35. inline void UpdateIndicatorControl(void)
36.          {
37.             static bool bTest = false;
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (bTest)
44.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
45.                else
46.                {
47.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
48.                      m_IndControl.Memory.dValue = Buff[0];
49.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
50.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
51.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
52.                }
53.             }else
54.             {
55.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
56.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
57.                m_IndControl.Memory._8b[7] = 'D';
58.                m_IndControl.Memory._8b[6] = 'M';
59.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
60.                bTest = false;
61.             }
62.          }
63. //+------------------------------------------------------------------+

C_Replay.mqh Quellcode-Ausschnitt

Sie haben vielleicht bemerkt, dass sich dieser Code deutlich von dem unterscheidet, den wir zuvor besprochen haben. Der Hauptgrund für diese Änderungen ist die Einführung eines sichereren Ansatzes für den Zugriff auf Elemente im Zusammenhang mit dem Modul des Kontrollindikators.

Um zu verstehen, wie dieses Fragment funktioniert, müssen Sie sich einige wichtige Punkte ins Gedächtnis rufen:

1) Die Werte werden im Konstruktor initialisiert; 2) die erste Funktion, die diese Routine aufruft, ist diejenige, die das Modul des Modul des Kontrollindikators initialisiert; 3) die Schleifenprozedur prüft in regelmäßigen Abständen den Zustand der Schaltfläche im Kontrollanzeiger.

Diese drei Schritte folgen dieser Reihenfolge, wobei der dritte Schritt der problematischste ist. Er ist nämlich dafür verantwortlich, neue Ticks in den Chart zu injizieren und den Kontrollindikator kontinuierlich zu überwachen. Die Möglichkeit, Daten direkt aus dem Objekt im Chart zu lesen, anstatt auf den Puffer zuzugreifen und Ereignisse nur in bestimmten Fällen an den Kontrollindikator zu senden, kann jedoch verhindern, dass die Prozedur UpdateIndicatorControl in kritischen Phasen des Wiedergabe-/Simulationsdienstes einen Leistungsabfall verursacht.

Schauen wir uns an, wie dieses Fragment funktioniert. Zunächst prüfen wir in Zeile 40, ob wir einen gültiges Handle haben. Wenn ja, wird der Vorgang fortgesetzt. Der nächste Schritt ist die Überprüfung, ob der Speicherwert mit der Position übereinstimmt, was in Zeile 41 geschieht. Wenn ja, prüfen wir in Zeile 43, ob die statische Variable wahr ist. Ist dies der Fall, verwenden wir eine Objektzugriffsfunktion, um den aktuellen Wert von OBJ_BITMAP_LABEL zu ermitteln. Achten Sie genau darauf, wie das gemacht wird. Es mag ungewöhnlich erscheinen, da wir uns auf ein Element aus der Header-Datei C_Controls.mqh beziehen. Dennoch findet der Zugang tatsächlich statt.

Wenn die statische Variable den Wert „false“ hat, bedeutet dies, dass es kein Problem ist, die Daten etwas langsamer zu lesen. In diesem Fall werden die Daten aus dem Puffer des Kontrollindikators abgerufen. Wichtiger Hinweis: Das bedeutet nicht, dass das Lesen des Puffers von Natur aus langsamer ist, aber wenn man die Anzahl der beteiligten Operationen vergleicht, ist das Lesen einer direkten Eigenschaft des grafischen Objekts eine einfachere Aufgabe.

Sobald der Puffer gelesen ist, wird in Zeile 49 geprüft, ob wir uns nicht im TriState-Modus befinden. Wenn diese Bedingung erfüllt ist, führen wir in Zeile 50 eine Reihe von Operationen aus, bevor wir feststellen, ob wir uns im Spielmodus befinden, wodurch die statische Variable auf true oder false gesetzt wird. Diese Operationen, bei denen es sich eigentlich um Variablenzuweisungen handelt, sind so strukturiert, dass der Befehl komplexer erscheinen könnte, als er tatsächlich ist. Da dies jedoch für den Compiler keine Rolle spielt und die Werte wie erwartet zugewiesen werden, können wir es so strukturieren. Wenn Zeile 50 den Wert true ergibt, speichern wir den vorherigen Wert des Puffers in der internen Positionsvariablen. Dies geschieht in Zeile 51.

Diese Abfolge von Vorgängen tritt nur in einer einzigen Situation auf: wenn der Nutzer mit dem Schieberegler interagiert und die Position ändert, an der die Wiedergabe/der Simulator beginnen soll. Mit anderen Worten: Wenn wir uns im Spielmodus befinden, wird dieser Code nicht ausgeführt. Beim Übergang vom Pausemodus in den Wiedergabemodus wird dieser Code jedoch ausgelöst, was später wichtig sein wird.

Wenn die Bedingung in Zeile 41 als falsch bewertet wird, werden die Anweisungen zwischen den Zeilen 55 und 60 ausgeführt. Dadurch wird ein nutzerdefiniertes Ereignis ausgelöst, um das Modul des Kontrollindikators zu aktualisieren. So wird das System in Zukunft funktionieren.

Das Lesen des Objekts direkt aus dem Chart verbessert nicht unbedingt die Leistung. Es eröffnet jedoch neue Möglichkeiten für diejenigen, die Chartobjekte effizient manipulieren wollen. Dieser Ansatz ermöglicht die Entwicklung anspruchsvollerer Tools, ohne die MetaTrader 5-Plattform zu überlasten, und macht es überflüssig, Charts mit unnötigen Indikatoren zu überladen, die nur der Objektmanipulation dienen.


Erzielung echter Leistungsgewinne

Trotz aller bisher diskutierten Verbesserungen bringen sie keine wesentliche Leistungssteigerung für den Replay/Simulationsdienst. Zumindest sind keine größeren Veränderungen zu beobachten. Diese Änderungen tragen jedoch dazu bei, bestimmte Abschnitte des Kodex zu straffen und effizienter zu gestalten. Der wichtigste Vorteil ist die bessere Lesbarkeit. Dies ist auf die Definitionen in der Datei C_Controls.mqh zurückzuführen, die dann in der Header-Datei C_Replay.mqh übernommen wurden. Auch wenn Sie noch nicht den gesamten Code durchgesehen haben, können Sie wahrscheinlich schon erahnen, wo Änderungen vorgenommen werden sollten, um die Lesbarkeit der Klasse C_Replay zu verbessern.

Lassen Sie uns nun eine Änderung erforschen, die die Leistung wirklich steigert. Ziel ist es, die Funktionalität der Erzeugung eines einminütigen Balkens innerhalb des erwarteten Zeitraums wiederherzustellen.

Auf den ersten Blick mag die vorgeschlagene Änderung bizarr und völlig kontraintuitiv erscheinen. Aber glauben Sie mir, Sie können es selbst testen. Diese einfache Änderung führt zu einem erheblichen Leistungsgewinn. Um es in Aktion zu sehen, lassen Sie uns den kompletten Code der Header-Datei C_Replay.mqh untersuchen. Nachstehend finden Sie die vollständige Fassung. Die Zeilennummerierung unterscheidet sich geringfügig von den vorangegangenen Codeschnipseln, aber darauf sollten Sie nicht achten. Schauen wir uns den Code an.

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

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Von der Grundstufe bis zur Mittelstufe: IF ELSE Von der Grundstufe bis zur Mittelstufe: IF ELSE
In diesem Artikel geht es um die Arbeit mit dem Operator IF und seinem Pendant ELSE. Diese Anweisung ist die wichtigste und aussagekräftigste, die es in jeder Programmiersprache gibt. Trotz ihrer einfachen Handhabung kann sie jedoch manchmal verwirrend sein, wenn man keine Erfahrung mit ihrer Verwendung und den damit verbundenen Konzepten hat. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Neuronale Netze im Handel: Vereinheitlichtes Trajektoriengenerierungsmodell (UniTraj) Neuronale Netze im Handel: Vereinheitlichtes Trajektoriengenerierungsmodell (UniTraj)
Das Verständnis des Agentenverhaltens ist in vielen verschiedenen Bereichen wichtig, aber die meisten Methoden konzentrieren sich nur auf eine der Aufgaben (Verstehen, Rauschunterdrückung oder Vorhersage), was ihre Effektivität in realen Szenarien verringert. In diesem Artikel werden wir uns mit einem Modell vertraut machen, das sich an die Lösung verschiedener Probleme anpassen lässt.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Ensemble-Methoden zur Verbesserung von Klassifizierungsaufgaben in MQL5 Ensemble-Methoden zur Verbesserung von Klassifizierungsaufgaben in MQL5
In diesem Artikel stellen wir die Implementierung mehrerer Ensemble-Klassifikatoren in MQL5 vor und erörtern ihre Wirksamkeit in verschiedenen Situationen.