
Entwicklung eines Replay Systems (Teil 52): Die Dinge werden kompliziert (IV)
Einführung
Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 51): Die Dinge werden kompliziert (III)“, haben wir einige Änderungen an unserem alten Mauszeiger vorgenommen, damit er in unserem Replay/Simulator-System richtig funktioniert. Im selben Artikel habe ich in der Praxis den Unterschied zwischen dem Abrufen einer Chart-ID mit ChartOpen, einer Funktion, die von Programmen verwendet wird, um MetaTrader 5 anzuweisen, ein Chart zu öffnen, und dem Abrufen derselben ID eines bereits geöffneten Charts, aber unter Verwendung der ChartID-Funktion, demonstriert.
Ich denke, es war ziemlich klar, dass der Unterschied und die langfristigen Möglichkeiten es Ihnen erlauben werden, die MQL5-Programmierung viel breiter einzusetzen.
Trotz aller Änderungen an Mauszeiger und Kontrollindikator gab es einige Schwierigkeiten bei der Integration, genauer gesagt bei der korrekten Interaktion von Mauszeiger und Kontrollanzeige. Das lag an meinem Fehler, denn bei der Entwicklung des Mauszeigers habe ich einige Details übersehen, wodurch die Interaktion nicht richtig funktioniert hat.
In diesem Artikel werden wir dies beheben: Keine Sorge, es ist eine ziemlich einfache, aber sehr notwendige Sache. Nachdem wir diese Änderung vorgenommen haben, können wir denselben Mauszeiger in den persönlichen Projekten verwenden. Auf diese Weise probiere ich ein personalisiertes Lernsystem und gleichzeitig eine sehr sichere Art der Interaktion mit der Grafik aus. Wenn sich der Mauszeiger im Studienmodus befindet, kann man nicht mit anderen Objekten im Chart interagieren. Dazu müssen wir sie jedoch lokal in unseren Code integrieren.
Um zu veranschaulichen, wie diese Integration erfolgen sollte, da wir dasselbe mit anderen Teilen des Wiedergabe-/Simulatorsystems tun werden, werden wir einen Kontrollindikator verwenden. Dieser Kontrollindikator wurde jedoch geändert, um die Verwendung eines geeigneteren Systems zu ermöglichen, da wir bisher das Basissystem MQL5 verwendet haben. Wir werden ein wenig tiefer in MQL5 eintauchen, und wir werden einige Funktionen haben, die wir vorher nicht bekommen konnten.
Die Interaktion, die wir in diesem Artikel erreichen werden, wird grundlegend sein, aber sie wird ausreichen, damit Sie verstehen, wie Sie den Mauszeiger in Ihre Programme integrieren können und so ein maßgeschneidertes Lernsystem erhalten, das gleichzeitig nicht instabil mit Objekten auf dem Chart interagiert. Beginnen wir also diesen Artikel mit dem ersten Thema, in dem wir uns die Änderungen ansehen, die am Mauszeiger vorgenommen werden müssen.
Verbesserungen am Mauszeiger
Der gesamte Code (und ich lasse ihn vorerst so, wie er ist) wird in dem Artikel vollständig veröffentlicht werden. Man könnte meinen, dass er in zu viele Teile aufgeteilt ist. Auf diese Weise ist es jedoch einfacher, alle Einzelheiten zu erklären. Wenn der Code also auf etwas verweist, das hier nicht steht, müssen Sie nach Verweisen in früheren Artikeln suchen.
Auf diese Weise kann ich mich darauf verlassen, dass Sie den Erklärungen folgen und verstehen, wie ich den Code entwickle. Mit dieser kurzen Erläuterung im Hinterkopf wollen wir uns den Mauszeiger-Code ansehen. Er ist unten aufgeführt:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property description "This is an indicator for graphical studies using the mouse." 004. #property description "This is an integral part of the Replay / Simulator system." 005. #property description "However it can be used in the real market." 006. #property version "1.52" 007. #property icon "/Images/Market Replay/Icons/Indicators.ico" 008. #property link "https://www.mql5.com/de/articles/11925" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. #property indicator_buffers 1 012. //+------------------------------------------------------------------+ 013. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 014. //+------------------------------------------------------------------+ 015. C_Study *Study = NULL; 016. //+------------------------------------------------------------------+ 017. input long user00 = 0; //ID 018. input C_Study::eStatusMarket user01 = C_Study::eAuction; //Market Status 019. input color user02 = clrBlack; //Price Line 020. input color user03 = clrPaleGreen; //Positive Study 021. input color user04 = clrLightCoral; //Negative Study 022. //+------------------------------------------------------------------+ 023. C_Study::eStatusMarket m_Status; 024. int m_posBuff = 0; 025. double m_Buff[]; 026. //+------------------------------------------------------------------+ 027. int OnInit() 028. { 029. ResetLastError(); 030. Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04); 031. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 032. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 033. { 034. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 035. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 036. m_Status = C_Study::eCloseMarket; 037. }else 038. m_Status = user01; 039. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 040. ArrayInitialize(m_Buff, EMPTY_VALUE); 041. 042. return INIT_SUCCEEDED; 043. } 044. //+------------------------------------------------------------------+ 045. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 046. { 047. m_posBuff = rates_total - 5; 048. (*Study).Update(m_Status); 049. 050. return rates_total; 051. } 052. //+------------------------------------------------------------------+ 053. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 054. { 055. (*Study).DispatchMessage(id, lparam, dparam, sparam); 056. SetBuffer(); 057. 058. ChartRedraw((*Study).GetInfoTerminal().ID); 059. } 060. //+------------------------------------------------------------------+ 061. void OnBookEvent(const string &symbol) 062. { 063. MqlBookInfo book[]; 064. C_Study::eStatusMarket loc = m_Status; 065. 066. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 067. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 068. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 069. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 070. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 071. if (loc != m_Status) (*Study).Update(m_Status); 072. } 073. //+------------------------------------------------------------------+ 074. void OnDeinit(const int reason) 075. { 076. if (reason != REASON_INITFAILED) 077. { 078. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 079. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 080. } 081. delete Study; 082. } 083. //+------------------------------------------------------------------+ 084. inline void SetBuffer(void) 085. { 086. uCast_Double Info; 087. 088. m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff); 089. m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price; 090. Info._datetime = (*Study).GetInfoMouse().Position.dt; 091. m_Buff[m_posBuff + 1] = Info.dValue; 092. Info._int[0] = (*Study).GetInfoMouse().Position.X_Adjusted; 093. Info._int[1] = (*Study).GetInfoMouse().Position.Y_Adjusted; 094. m_Buff[m_posBuff + 2] = Info.dValue; 095. Info._int[0] = (*Study).GetInfoMouse().Position.X_Graphics; 096. Info._int[1] = (*Study).GetInfoMouse().Position.Y_Graphics; 097. m_Buff[m_posBuff + 3] = Info.dValue; 098. Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0); 099. m_Buff[m_posBuff + 4] = Info.dValue; 100. } 101. //+------------------------------------------------------------------+
Mauszeiger-Quellcode
Höchstwahrscheinlich werden Sie keinen Unterschied zwischen diesem Code und den vorherigen feststellen. Wie ich bereits sagte, sind die Änderungen geringfügig, aber sie sind vorhanden.
Die erste und vielleicht einzige dieser Angaben ist der Index des Puffers, in den der Indikator geschrieben werden soll. In Zeile 47 des Codes können Sie sehen, dass der Offset-Wert, der vorher vier war, jetzt fünf ist. Was ist der Grund für diese Änderung? Die Erhöhung um eine Einheit ist notwendig, um einen größeren Erfassungsbereich zu schaffen, aber zum besseren Verständnis sollten wir uns die Funktion ansehen, die für die Einstellung des Indikatorpuffers in Zeile 84 verantwortlich ist.
Wahrscheinlich werden Sie keinen Unterschied zwischen dieser Funktion und den bisherigen Funktionen feststellen. Die Änderungen sind subtil. Sie sollten sie verstehen, um sie in Ihren persönlichen Projekten verwenden zu können, wenn Sie diesen Indikator in Ihr spezifisches Modell integrieren möchten.
Insbesondere ab Zeile 92 sehen die Dinge anders aus, aber nicht so sehr. Warum deklariere ich zwei X-Variablen und zwei Y-Variablen? Und warum heißt die eine „Adjusted“ und das andere „Graphics“? Das ist der springende Punkt. Zuvor lieferte der Indikator nur eine nach Zeit und Preis bereinigte grafische Variable, die jedoch nicht in allen Fällen geeignet ist. Es gibt Situationen, in denen wir den Wert einer grafischen Koordinate und nicht einen bestimmten Wert benötigen. Um beide Situationen abzudecken, gibt der Indikator diese beiden Werte zurück, damit wir sie bestmöglich nutzen können.
Bitte beachten Sie, dass die Nullposition des Puffers den Preiswert und die erste Position den Zeitwert enthält. Ich habe beschlossen, sie aus Gründen der Abwärtskompatibilität mit anderen Dingen, die ich persönlich bereits verwende, zu behalten. Es besteht jedoch keine Notwendigkeit, den Puffer so zu lesen, wie er erstellt wurde. Denken Sie daran, dass es auf einer höheren Ebene einen einfacheren Weg gibt, nämlich die Klasse Mouse zu verwenden, aber wenn Sie dennoch Daten direkt aus dem Puffer lesen wollen, müssen Sie verstehen, wie dieser aufgebaut ist. Daher ist das Verständnis dieser in Zeile 84 dargestellten Funktion von größter Bedeutung.
Die Veränderungen sind noch nicht zu Ende. Es ist wichtig, sich darüber im Klaren zu sein, dass sich Änderungen am Code auf einen großen Teil des restlichen Codes auswirken können. In der Klasse, die für die Erstellung und Durchführung der Studie verantwortlich ist, waren die Veränderungen jedoch nicht so radikal, aber dennoch erklärungsbedürftig. Der Studiencode ist nachstehend aufgeführt.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1" 008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2" 009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3" 010. //+------------------------------------------------------------------+ 011. class C_Study : public C_Mouse 012. { 013. private : 014. //+------------------------------------------------------------------+ 015. struct st00 016. { 017. eStatusMarket Status; 018. MqlRates Rate; 019. string szInfo; 020. color corP, 021. corN; 022. int HeightText; 023. }m_Info; 024. //+------------------------------------------------------------------+ 025. const datetime GetBarTime(void) 026. { 027. datetime dt; 028. u_Interprocess info; 029. int i0 = PeriodSeconds(); 030. 031. if (m_Info.Status == eInReplay) 032. { 033. if (!GlobalVariableGet(def_GlobalVariableServerTime, info.df_Value)) return ULONG_MAX; 034. if ((dt = info.ServerTime) == 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. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 047. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 048. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 049. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / GetInfoMouse().Position.Price) * 100.0), 2); 051. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 052. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 053. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0)) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 055. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 056. } 057. //+------------------------------------------------------------------+ 058. public : 059. //+------------------------------------------------------------------+ 060. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 061. :C_Mouse(IdParam, szShortName, corH, corP, corN) 062. { 063. if (_LastError != ERR_SUCCESS) return; 064. ZeroMemory(m_Info); 065. m_Info.Status = eCloseMarket; 066. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 067. m_Info.corP = corP; 068. m_Info.corN = corN; 069. CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 070. CreateObjectInfo(2, 53, def_ExpansionBtn2); 071. CreateObjectInfo(58, 53, def_ExpansionBtn3); 072. } 073. //+------------------------------------------------------------------+ 074. void Update(const eStatusMarket arg) 075. { 076. datetime dt; 077. 078. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 079. { 080. case eCloseMarket : m_Info.szInfo = "Closed Market"; 081. break; 082. case eInReplay : 083. case eInTrading : 084. if ((dt = GetBarTime()) < ULONG_MAX) 085. { 086. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 087. break; 088. } 089. case eAuction : m_Info.szInfo = "Auction"; 090. break; 091. default : m_Info.szInfo = "ERROR"; 092. } 093. Draw(); 094. } 095. //+------------------------------------------------------------------+ 096. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 097. { 098. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 099. if (id == CHARTEVENT_MOUSE_MOVE) Draw(); 100. } 101. //+------------------------------------------------------------------+ 102. }; 103. //+------------------------------------------------------------------+ 104. #undef def_ExpansionBtn3 105. #undef def_ExpansionBtn2 106. #undef def_ExpansionBtn1 107. #undef def_ExpansionPrefix 108. #undef def_MousePrefixName 109. //+------------------------------------------------------------------+
Quellcode der Klasse C_Study
Die einzigen wirklichen Änderungen in dieser Klasse wurden an der Funktion Draw vorgenommen, die sich in Zeile 42 befindet. Diese Änderungen zielen darauf ab, die Logik der zeit- und preisbasierten Forschung beizubehalten. Bei Objekten, deren Koordinatensystem grafisch ist, tritt dieser Fehler jedoch auf. Wir haben die Erklärung dieses Objekttyps bei der Erstellung des Mausindikators vor ein paar Artikeln gesehen. Ich habe erklärt, warum und wie man mit Preis-Zeit-Koordinaten und grafischen Koordinaten vom Typ XY arbeitet.
Um die Abwärtskompatibilität zu wahren, verwenden wir angepasste grafische Koordinaten. Dies ist in den Zeilen 46-48 zu sehen. Wenn wir jedoch andere Objekte verwenden, die Grafikkoordinaten benötigen und die den Preis-Zeit-Koordinaten folgen sollen, müssen wir einfach die angepassten Grafikkoordinaten verwenden, wie hier gezeigt. Wenn Sie dieses angepasste System durch ein nicht angepasstes grafisches System ersetzen, kann die Studie etwas anders aussehen.
Vielleicht sollten Sie das einmal selbst ausprobieren.
Um den Teil über den Mauszeiger abzuschließen, schauen wir uns die Klasse an, die für die Unterstützung des Basissystems verantwortlich ist. So können wir den Unterschied zwischen angepassten und nicht angepassten grafischen Koordinaten besser verstehen. Anschließend werden wir uns mit dem Code der Kontrollindikatoren befassen, um den Grund für all diese Änderungen zu verstehen. Der vollständige Code für die Mausklasse ist unten zu sehen. Ich hoffe, dass wir keine weiteren Änderungen daran vornehmen müssen.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. #include "Interprocess.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_MousePrefixName "MouseBase_" 008. #define def_NameObjectLineH def_MousePrefixName + "H" 009. #define def_NameObjectLineV def_MousePrefixName + "TV" 010. #define def_NameObjectLineT def_MousePrefixName + "TT" 011. #define def_NameObjectStudy def_MousePrefixName + "TB" 012. //+------------------------------------------------------------------+ 013. class C_Mouse : public C_Terminal 014. { 015. public : 016. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 017. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 018. struct st_Mouse 019. { 020. struct st00 021. { 022. int X_Adjusted, 023. Y_Adjusted, 024. X_Graphics, 025. Y_Graphics; 026. double Price; 027. datetime dt; 028. }Position; 029. uint ButtonStatus; 030. bool ExecStudy; 031. }; 032. //+------------------------------------------------------------------+ 033. protected: 034. enum eEventsMouse {ev_HideMouse, ev_ShowMouse}; 035. //+------------------------------------------------------------------+ 036. void CreateObjectInfo(int x, int w, string szName, color backColor = clrNONE) const 037. { 038. if (m_Mem.szShortName != NULL) return; 039. CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 044. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 046. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 047. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 048. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 049. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 050. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 051. } 052. //+------------------------------------------------------------------+ 053. private : 054. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 055. struct st01 056. { 057. st_Mouse Data; 058. color corLineH, 059. corTrendP, 060. corTrendN; 061. eStudy Study; 062. }m_Info; 063. struct st_Mem 064. { 065. bool CrossHair, 066. IsFull; 067. datetime dt; 068. string szShortName; 069. }m_Mem; 070. bool m_OK; 071. //+------------------------------------------------------------------+ 072. void GetDimensionText(const string szArg, int &w, int &h) 073. { 074. TextSetFont("Lucida Console", -100, FW_NORMAL); 075. TextGetSize(szArg, w, h); 076. h += 5; 077. w += 5; 078. } 079. //+------------------------------------------------------------------+ 080. void CreateStudy(void) 081. { 082. if (m_Mem.IsFull) 083. { 084. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 085. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 086. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 087. CreateObjectInfo(0, 0, def_NameObjectStudy); 088. } 089. m_Info.Study = eStudyCreate; 090. } 091. //+------------------------------------------------------------------+ 092. void ExecuteStudy(const double memPrice) 093. { 094. double v1 = GetInfoMouse().Position.Price - memPrice; 095. int w, h; 096. 097. if (!CheckClick(eClickLeft)) 098. { 099. m_Info.Study = eStudyNull; 100. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 101. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 102. }else if (m_Mem.IsFull) 103. { 104. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 105. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0))); 106. GetDimensionText(sz1, w, h); 107. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 110. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 111. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 112. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 113. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 114. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 115. } 116. m_Info.Data.ButtonStatus = eKeyNull; 117. } 118. //+------------------------------------------------------------------+ 119. public : 120. //+------------------------------------------------------------------+ 121. C_Mouse(const long id, const string szShortName) 122. :C_Terminal(id), 123. m_OK(false) 124. { 125. m_Mem.szShortName = szShortName; 126. } 127. //+------------------------------------------------------------------+ 128. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 129. :C_Terminal(id) 130. { 131. if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown); 132. if (_LastError != ERR_SUCCESS) return; 133. m_Mem.szShortName = NULL; 134. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 135. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 136. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 137. ZeroMemory(m_Info); 138. m_Info.corLineH = corH; 139. m_Info.corTrendP = corP; 140. m_Info.corTrendN = corN; 141. m_Info.Study = eStudyNull; 142. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 143. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 144. } 145. //+------------------------------------------------------------------+ 146. ~C_Mouse() 147. { 148. if (!m_OK) return; 149. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false); 150. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 151. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 152. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 153. } 154. //+------------------------------------------------------------------+ 155. inline bool CheckClick(const eBtnMouse value) 156. { 157. return (GetInfoMouse().ButtonStatus & value) == value; 158. } 159. //+------------------------------------------------------------------+ 160. inline const st_Mouse GetInfoMouse(void) 161. { 162. if (m_Mem.szShortName != NULL) 163. { 164. double Buff[]; 165. uCast_Double loc; 166. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 167. 168. ZeroMemory(m_Info.Data); 169. if (CopyBuffer(handle, 0, 0, 5, Buff) == 5) 170. { 171. m_Info.Data.Position.Price = Buff[0]; 172. loc.dValue = Buff[1]; 173. m_Info.Data.Position.dt = loc._datetime; 174. loc.dValue = Buff[2]; 175. m_Info.Data.Position.X_Adjusted = loc._int[0]; 176. m_Info.Data.Position.Y_Adjusted = loc._int[1]; 177. loc.dValue = Buff[3]; 178. m_Info.Data.Position.X_Graphics = loc._int[0]; 179. m_Info.Data.Position.Y_Graphics = loc._int[1]; 180. loc.dValue = Buff[4]; 181. m_Info.Data.ButtonStatus = loc._char[0]; 182. IndicatorRelease(handle); 183. } 184. } 185. 186. return m_Info.Data; 187. } 188. //+------------------------------------------------------------------+ 189. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 190. { 191. int w = 0; 192. static double memPrice = 0; 193. 194. if (m_Mem.szShortName == NULL) switch (id) 195. { 196. case (CHARTEVENT_CUSTOM + ev_HideMouse): 197. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 198. break; 199. case (CHARTEVENT_CUSTOM + ev_ShowMouse): 200. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 201. break; 202. case CHARTEVENT_MOUSE_MOVE: 203. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (int)lparam, m_Info.Data.Position.Y_Graphics = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 204. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 205. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 206. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X_Adjusted, m_Info.Data.Position.Y_Adjusted); 207. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 208. m_Info.Data.ButtonStatus = (uint) sparam; 209. if (CheckClick(eClickMiddle)) 210. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 211. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 212. { 213. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 214. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 215. m_Info.Study = eStudyExecute; 216. } 217. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 218. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 219. break; 220. case CHARTEVENT_OBJECT_DELETE: 221. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 222. break; 223. } 224. } 225. //+------------------------------------------------------------------+ 226. }; 227. //+------------------------------------------------------------------+ 228. #undef def_NameObjectLineV 229. #undef def_NameObjectLineH 230. #undef def_NameObjectLineT 231. #undef def_NameObjectStudy 232. //+------------------------------------------------------------------+
Quellcode der Klasse C_Mouse
Sie werden feststellen, dass die Situation hier angespannt zu sein scheint, aber lassen Sie sich nicht vom ersten Eindruck täuschen. Kommen wir nun zu den ersten Details. Die in den Zeilen 4 und 5 enthaltenen Angaben können aus früheren Artikeln entnommen werden. Sie haben sich nicht geändert, sodass es keinen Sinn hat, sie hier zu wiederholen.
Wenn wir ein wenig nach unten blättern, finden wir in den Zeilen 22 bis 25 die Variablendeklarationen, die wir in den vorherigen Codes gesehen haben. Sie sind Teil einer Struktur, die öffentlich ist. So können wir in Zukunft die gleiche Struktur verwenden. Dadurch wird der Code besser lesbar und von höherer Qualität. Dieselben Variablen werden in den Zeilen 110 und 111 erneut verwendet, um sicherzustellen, dass einige Objekte korrekt positioniert sind.
Aber erst in Zeile 157 wird es richtig interessant. Wie wir bereits gesehen haben, müssen Sie nicht wissen, wie der Puffer montiert wurde, um die Mausklasse zu verwenden, um die benötigten Werte zu erhalten. Möglich wird dies durch die Verwendung dieser Funktion in Zeile 157. Diese Funktion ist in der Lage, sich anzupassen und korrekt auf eine Pufferleseanforderung zu reagieren, indem sie Informationen über die oben genannte Struktur zurückgibt. Das macht die Programmierung viel einfacher.
Aber beachten Sie, dass wir hier, genau wie bei der Erstellung des Puffers, in umgekehrter Richtung arbeiten müssen, um die Informationen zu erhalten, die im Puffer vorhanden sind. Wenn alles richtig läuft, liefert die Funktion GetInfoMouse Daten zur Mauspositionierung und zu den Mausklicks. Diese Daten können in jedem von Ihnen entwickelten Programm oder im Mauszeiger selbst verwendet werden. Die Schnittstelle ist dieselbe, was die Wartung und das Verständnis des Codes erheblich erleichtert.
Nach dieser Mausfunktion GetInfoMouse folgt eine weitere Funktion, die vielleicht die wichtigste von allen ist, da sie auf Interaktionen mit der Maus reagiert. Diese Funktion, DispatchMessage, befindet sich in Zeile 186, aber sie ist im Wesentlichen dieselbe wie vorher, nur mit einigen Verbesserungen, die genau das tun, was wir brauchen.
Es gibt ein paar Dinge zu verstehen: MetaTrader 5 verwendet ein Nachrichtensystem, das dem von Windows sehr ähnlich ist. Aus diesem Grund werden die Nachrichten an unser Programm auf die gleiche Weise weitergeleitet, als ob es unter Windows laufen würde. Die Kenntnis der Windows-Programmierung ist in diesen Fällen eine große Hilfe, aber selbst wenn die Informationen bei Bedarf überprüft werden, landen sie oft in der falschen Form oder in einem Format, das denjenigen, die mit der Programmierung nicht vertraut sind, fremd ist.
Wenn MetaTrader 5 also ein Mausereignis an unser Programm sendet, kompiliert es die Mausdaten in die Nachricht selbst. Dies ist in Zeile 200 zu sehen, wo MetaTrader 5 genau prüft, was Windows ihm sagt. MetaTrader 5 meldet also nicht die Mauskoordinaten auf Basis von Preis und Zeit, sondern in grafischer Form. In diesem Stadium haben wir die notwendigen grafischen Koordinaten, die nicht angepasst sind und die Position der Maus im Fenster darstellen. Nicht auf dem Chart, sondern im Fenster. Es gibt jedoch etwas, das geklärt werden muss. Das Wort „window“ (Fenster) bezieht sich hier auf Grafikfenster. Um dies zu verstehen, müssen Sie sich mit der Windows-Programmierung auskennen, was nicht der Zweck dieses Artikels ist.
Sobald wir diese Position haben, können wir MQL5 verwenden, um sie in Preis- und Zeitpositionen umzuwandeln. Aus diesem Grund kann es vorkommen, dass Objekte, die diese Koordinaten zur Positionierung verwenden, etwas vom Punkt entfernt liegen und keine direkte Verbindung zu den Balken auf dem Chart haben. Um dies zu beheben, verwenden wir Zeile 201, die den Preis anpasst, und Zeile 202, die die Zeit anpasst. Dadurch wird eine Übereinstimmung zwischen der Position und dem Balken im Chart hergestellt.
Da wir aber an einigen Stellen die X- und Y-Werte um Preis und Zeit bereinigen wollen, verwenden wir Zeile 203. Auf diese Weise erhalten wir angepasste Werte. Zuvor wurde nicht zwischen dem grafisch angepassten und dem nicht angepassten Wert unterschieden. Bei dem Versuch, Objekte zu manipulieren und den Mausindikator mit dem Kontrollindikator zu verbinden, war es jedoch notwendig, diese Unterscheidung zu treffen. Damit wird der Mauszeiger geschlossen und wir können unsere Aufmerksamkeit auf den Kontrollindikator richten.
Ein neuer Kontrollindikator
Wie wir zu Beginn des Artikels erwähnt haben, werden wir MQL5 intensiver nutzen, um einige Verbesserungen am Kontrollindikator voranzutreiben. Diese Verbesserungen werden uns helfen, das Leben ein wenig angenehmer zu gestalten. In der Anwendung werden wir Zugang zu neuen Formen haben, mit denen wir Transparenz erreichen können. Um zu verdeutlichen, wie dies erreicht werden kann, werden wir neue Rasterbilder verwenden.
Sie fragen sich vielleicht: Muss ich dafür eine neue Klasse erstellen? Keine Sorge, ich verspreche, dass ich Ihnen am Ende des Artikels die wichtigsten Interaktionen zeigen werde.
Der Quellcode für die Klasse lautet wie folgt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_Terminal.mqh" 05. //+------------------------------------------------------------------+ 06. #define def_MaxImages 2 07. //+------------------------------------------------------------------+ 08. class C_DrawImage : protected C_Terminal 09. { 10. //+------------------------------------------------------------------+ 11. private : 12. struct st_00 13. { 14. int widthMap, 15. heightMap; 16. uint Map[]; 17. }m_InfoImage[def_MaxImages]; 18. uint m_Pixels[]; 19. string m_szObjName, 20. m_szRecName; 21. //+------------------------------------------------------------------+ 22. void ReSizeImage(const int w, const int h, const uchar v, const int what) 23. { 24. #define _Transparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0) 25. double fx = (w * 1.0) / m_InfoImage[what].widthMap; 26. double fy = (h * 1.0) / m_InfoImage[what].heightMap; 27. uint pyi, pyf, pxi, pxf, tmp; 28. uint uc; 29. 30. ArrayResize(m_Pixels, w * h); 31. for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap) 32. { 33. pyf = (uint)(fy * cy) * w; 34. tmp = pyi = (uint)(fy * (cy - 1)) * w; 35. for (int x = 0; x < m_InfoImage[what].widthMap; x++) 36. { 37. pxf = (uint)(fx * x); 38. pxi = (uint)(fx * (x - 1)); 39. uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * _Transparency(v)) << 24) | uc & 0x00FFFFFF; 40. m_Pixels[pxf + pyf] = uc; 41. for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc; 42. } 43. for (pyi += w; pyi < pyf; pyi += w) 44. for (int x = 0; x < w; x++) 45. m_Pixels[x + pyi] = m_Pixels[x + tmp]; 46. } 47. #undef _Transparency 48. } 49. //+------------------------------------------------------------------+ 50. public : 51. //+------------------------------------------------------------------+ 52. C_DrawImage(long id, int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL) 53. :C_Terminal(id), 54. m_szObjName(NULL), 55. m_szRecName(NULL) 56. { 57. if (!ObjectCreate(GetInfoTerminal().ID, m_szObjName = szObjName, OBJ_BITMAP_LABEL, sub, 0, 0)) SetUserError(C_Terminal::ERR_Unknown); 58. m_szRecName = "::" + m_szObjName; 59. for (int c0 = 0; (c0 < def_MaxImages) && (_LastError == ERR_SUCCESS); c0++) 60. { 61. ResourceReadImage((c0 == 0 ? szFile1 : (szFile2 == NULL ? szFile1 : szFile2)), m_InfoImage[c0].Map, m_InfoImage[c0].widthMap, m_InfoImage[c0].heightMap); 62. ArrayResize(m_Pixels, m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap); 63. ArrayInitialize(m_Pixels, 0); 64. for (int c1 = (m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap) - 1; c1 >= 0; c1--) 65. if ((m_InfoImage[c0].Map[c1] & 0x00FFFFFF) != cFilter) m_Pixels[c1] = m_InfoImage[c0].Map[c1]; 66. ArraySwap(m_InfoImage[c0].Map, m_Pixels); 67. } 68. ArrayResize(m_Pixels, 1); 69. } 70. //+------------------------------------------------------------------+ 71. ~C_DrawImage() 72. { 73. for (int c0 = 0; c0 < def_MaxImages; c0++) 74. ArrayFree(m_InfoImage[c0].Map); 75. ArrayFree(m_Pixels); 76. ObjectDelete(GetInfoTerminal().ID, m_szObjName); 77. ResourceFree(m_szRecName); 78. } 79. //+------------------------------------------------------------------+ 80. void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what) 81. { 82. 83. if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return; 84. ReSizeImage(w, h, cView, what); 85. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x); 86. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y); 87. if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) 88. { 89. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName); 90. ChartRedraw(GetInfoTerminal().ID); 91. } 92. } 93. //+------------------------------------------------------------------+ 94. }; 95. //+------------------------------------------------------------------+ 96. #undef def_MaxImages 97. //+------------------------------------------------------------------+
Quellcode der Klasse C_DrawImage
Beachten Sie, dass der Code sehr kompakt ist. Dies liegt daran, dass wir keine externen Ressourcen verwenden werden. Wir verfügen über MQL5 und alles, was es uns bietet. Im Wesentlichen ist diese Klasse eine alternative Möglichkeit zur Verwendung von OBJ_BITMAP_LABEL- oder OBJ_BITMAP-Objekten. Beide Typen sind Teil der MQL5-Standardbibliothek, aber sie haben nicht die Möglichkeit, Transparenz oder unsichtbare Punkte auf dem Bild zu verwenden. Die obige Klasse kann also erweitert werden, um die Dinge zu integrieren, die wir brauchen.
Diese Klasse ist so einfach, dass wir nur vier unkomplizierte Verfahren verwenden. Lassen Sie mich kurz erklären, wie das Ganze hier funktioniert.
In Zeile 6 geben wir die maximale Anzahl der Bilder an, die wir verwenden wollen. Es ist möglich, diese Zahl zu erhöhen, aber dazu sind einige Codeänderungen erforderlich. Bald werde ich Ihnen zeigen, wo Sie Änderungen vornehmen müssen. Dann gehen wir in den Klassencode in Zeile 8 über. Beachten Sie, dass sie von der Klasse Terminal abgeleitet wird. Dies wurde in früheren Artikeln ausführlich erläutert.
Unmittelbar danach, in Zeile 11, verwenden wir den privaten Teil des Codes, um anzuzeigen, dass die deklarierten Daten für den inneren Code der Klasse privat sein werden. Eigentlich brauchen wir diesen Teil, in dem solche Daten deklariert werden, nicht zu ändern. In Zeile 22 beginnen wir jedoch mit dem Pre-Rendering-Verfahren, bei dem wir die Bildgröße und die Transparenzstufe festlegen. Dieser Wert variiert von 0 % bis 100 % und ändert sich in ganzzahligen Schritten, d. h. jeweils um einen Schritt. Die Definition von Zeile 24 stellt sicher, dass die Transformation der Bildtransparenz korrekt ist.
Hier erhöhen oder verringern wir die Bildauflösung. Da es sich um kleine Bilder handelt, habe ich keine Notwendigkeit gesehen, ein Anti-Aliasing hinzuzufügen. Wenn Sie also zu stark zoomen, kann das Bild sehr pixelig erscheinen. Wenn Sie das Bild aus irgendeinem Grund vergrößern wollen, sollten Sie ein Anti-Aliasing in Betracht ziehen, aber das derzeitige System ist für unsere Zwecke völlig ausreichend.
Zeile 52 enthält den Klassenkonstruktor. An dieser Stelle werden Sie die Anzahl der Parameter erhöhen müssen, wenn Sie mit mehr Bildern arbeiten wollen, aber auch hier geht es darum, die Möglichkeiten von MQL5 zu erweitern. Daher sind zwei Bilder ausreichend.
In diesem Konstruktor, in Zeile 61, greifen wir auf die Bilder zu, die wir als Ressourcen referenzieren. Denken Sie daran, dass unser System keine externen Ressourcen benötigt, sodass nur ausführbare Dateien transportiert werden können. Zeile 63 sorgt dafür, dass das Bild vollständig transparent ist. Außerdem führen wir in Zeile 64 eine Schleife ein, die das geladene Bild durchläuft, und prüfen bei jeder Interaktion in Zeile 65, ob die angegebene Farbe als transparent gefunden wird. Wenn true, wird die Farbe nicht hinzugefügt, wenn false, wird sie hinzugefügt.
In Zeile 66 verwenden wir die Funktion MQL5, um das ursprüngliche Bild in das geänderte umzuwandeln, damit alles so schnell wie möglich geht. In Zeile 68 wird das System bereinigt.
Der Destruktor am Anfang von Zeile 71 wird verwendet, um alle Ressourcen (in diesem Fall Speicher) an das System zurückzugeben, damit sie von anderen Programmen verwendet werden können. Es sind keine besonderen Angaben oder Erklärungen erforderlich.
Zeile 80 enthält den Zeichenvorgang. Dieses Verfahren bewirkt, dass das Bild auf dem Chart an der angegebenen Position und in den angegebenen Abmessungen reproduziert wird, wobei die ersten vier Parameter als Richtlinien dienen. Der fünfte Aufrufparameter muss ein Wert zwischen 0 und 100 sein, wobei 0 für keine Transparenz und 100 für volle Transparenz steht. Der sechste Parameter gibt an, welches Bild tatsächlich gerendert werden soll. Der Wert sollte immer mit 0 beginnen, dies ist der Index des ersten Bildes, und der Höchstwert ist die höchste Zahl minus 1. Das heißt, wir verwenden dasselbe System wie bei den Objekten OBJ_BITMAP_LABEL und OBJ_BITMAP, in denen wir den Bildindex angeben, nur dass es hier mehr als 2 sein können, wenn wir die von mir angegebenen Punkte ändern. Diese Zeichenfunktion bedarf keiner Erläuterung oder zusätzlicher Änderungen, wenn wir die Möglichkeiten erweitern wollen.
Die einzigen Stellen, die geändert werden müssen, sind der Konstruktor und die Definition in Zeile 6. Wenn wir nun ein Bild direkt aus einer Datei laden wollen, müssen wir auch das implementieren. Für das, was wir tun wollen, ist diese Klasse bereits perfekt.
Nun können wir mit dem Code für den Indikator und die Kontrollklasse fortfahren. Doch bevor wir uns den Code der Kontrollklasse ansehen, wollen wir einen kurzen Blick auf den Indikatorcode werfen. Unten können Sie ihn in voller Länge sehen.
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.52" 07. #property link "https://www.mql5.com/de/articles/11925" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. C_Controls *control = NULL; 14. //+------------------------------------------------------------------+ 15. input long user00 = 0; //ID 16. //+------------------------------------------------------------------+ 17. int OnInit() 18. { 19. u_Interprocess Info; 20. 21. ResetLastError(); 22. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID) 23. SetUserError(C_Terminal::ERR_PointerInvalid); 24. if (_LastError != ERR_SUCCESS) 25. { 26. Print("Control indicator failed on initialization."); 27. return INIT_FAILED; 28. } 29. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 30. EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, ""); 31. GlobalVariableTemp(def_GlobalVariableReplay); 32. 33. return INIT_SUCCEEDED; 34. } 35. //+------------------------------------------------------------------+ 36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 37. { 38. return rates_total; 39. } 40. //+------------------------------------------------------------------+ 41. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 42. { 43. (*control).DispatchMessage(id, lparam, dparam, sparam); 44. } 45. //+------------------------------------------------------------------+ 46. void OnDeinit(const int reason) 47. { 48. switch (reason) 49. { 50. case REASON_TEMPLATE: 51. Print("Modified template. Replay/simulation system shutting down."); 52. case REASON_PARAMETERS: 53. case REASON_REMOVE: 54. case REASON_CHARTCLOSE: 55. if (ChartSymbol(user00) != def_SymbolReplay) break; 56. GlobalVariableDel(def_GlobalVariableReplay); 57. ChartClose(user00); 58. break; 59. } 60. delete control; 61. } 62. //+------------------------------------------------------------------+
Quellcode des Kontrollindikators
Sie können sehen, dass dieser Code etwas an Gewicht (Volumen) verloren hat. Aber das ist nur vorübergehend, denn wir brauchen einen leichteren Code, um ihn richtig zu testen. Sie können sehen, dass wir viel weniger Aufrufe und Verweise auf die Kontrollklasse haben. Warum? Der Grund dafür ist die veränderte Organisation des Prozesses. Wir sollten so wenige Zugangspunkte wie möglich haben. Beachten Sie, dass der OnInit-Code sehr unterschiedlich ist. Als Nächstes gibt es in Zeile 30 etwas sehr Ungewöhnliches. In diesem Schritt initialisieren wir die Daten der Kontrollklasse nach dem Konstruktor. Was jedoch den in Zeile 22 erwähnten Konstruktor betrifft, so haben wir beschlossen, dass er sich nur mit der Initialisierung der verwendeten Zeiger befassen soll. Alle anderen Aufgaben werden vom Ereignisbehandlung übernommen. Aus diesem Grund haben wir Zeile 30. Um die Dinge klarer zu machen, sehen wir uns den grundlegenden Code der Kontrollklasse an.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\Interprocess.mqh" 005. #include "..\Auxiliar\C_DrawImage.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_PrefixCtrlName "MarketReplayCTRL_" 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. enum EventCustom {evInit}; 036. private : 037. //+------------------------------------------------------------------+ 038. enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull}; 039. //+------------------------------------------------------------------+ 040. struct st_00 041. { 042. string szBarSlider, 043. szBarSliderBlock; 044. int Minimal; 045. }m_Slider; 046. struct st_01 047. { 048. C_DrawImage *Btn; 049. bool state; 050. int x, y, w, h; 051. }m_Section[eObjectControl::eNull]; 052. C_Mouse *m_MousePtr; 053. //+------------------------------------------------------------------+ 054. inline void CreteBarSlider(int x, int size) 055. { 056. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "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_PrefixCtrlName + "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_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 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) ? 0 : 1)); 078. ChartRedraw(GetInfoTerminal().ID); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateCtrlSlider(void) 082. { 083. CreteBarSlider(77, 436); 084. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 085. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 086. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(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_PrefixCtrlName + "B"); 099. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 100. } 101. //+------------------------------------------------------------------+ 102. inline void PositionPinSlider(int p) 103. { 104. int iL, iR; 105. 106. m_Section[ePin].x = (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 - (m_Section[ePin].w / 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(void) 117. { 118. C_Mouse::st_Mouse InfoMouse; 119. int x, y; 120. 121. InfoMouse = (*m_MousePtr).GetInfoMouse(); 122. x = InfoMouse.Position.X_Graphics; 123. y = InfoMouse.Position.Y_Graphics; 124. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 125. { 126. if ((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)) 127. return c0; 128. } 129. 130. return eNull; 131. } 132. //+------------------------------------------------------------------+ 133. public : 134. //+------------------------------------------------------------------+ 135. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 136. :C_Terminal(Arg0), 137. m_MousePtr(MousePtr) 138. { 139. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 141. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 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. } 153. //+------------------------------------------------------------------+ 154. ~C_Controls() 155. { 156. delete m_MousePtr; 157. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 158. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 159. } 160. //+------------------------------------------------------------------+ 161. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 162. { 163. u_Interprocess Info; 164. 165. switch (id) 166. { 167. case CHARTEVENT_CUSTOM + C_Controls::evInit: 168. Info.df_Value = dparam; 169. m_Slider.Minimal = Info.s_Infos.iPosShift; 170. SetPlay(Info.s_Infos.isPlay); 171. if (!Info.s_Infos.isPlay) CreateCtrlSlider(); 172. break; 173. case CHARTEVENT_OBJECT_DELETE: 174. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 175. { 176. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 177. { 178. delete m_Section[ePlay].Btn; 179. m_Section[ePlay].Btn = NULL; 180. SetPlay(false); 181. }else 182. { 183. RemoveCtrlSlider(); 184. CreateCtrlSlider(); 185. } 186. } 187. break; 188. case CHARTEVENT_MOUSE_MOVE: 189. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick()) 190. { 191. case ePlay: 192. SetPlay(!m_Section[ePlay].state); 193. if (m_Section[ePlay].state) RemoveCtrlSlider(); 194. else CreateCtrlSlider(); 195. break; 196. case eLeft: 197. break; 198. case eRight: 199. break; 200. case ePin: 201. break; 202. } 203. break; 204. } 205. ChartRedraw(GetInfoTerminal().ID); 206. } 207. //+------------------------------------------------------------------+ 208. }; 209. //+------------------------------------------------------------------+ 210. #undef def_PosXObjects 211. #undef def_ButtonPlay 212. #undef def_ButtonPause 213. #undef def_ButtonLeft 214. #undef def_ButtonRight 215. #undef def_ButtonPin 216. #undef def_PrefixCtrlName 217. #undef def_PathBMP 218. //+------------------------------------------------------------------+
Quellcode der Klasse C_Controls
Wir sagen „Basiscode“, weil er eigentlich nichts Nützliches tut. Es ermöglicht uns lediglich, die Verwendung der oben erwähnten Zeichenklasse und einige sehr einfache Interaktionen mit dem Mauszeiger zu demonstrieren. Schauen wir mal, was wir haben. In Zeile 26 legen wir die Größe der Interaktionsschaltflächen fest. In Zeile 27 geben wir die Farbe an, die wir verwenden werden, um anzuzeigen, dass ein Bereich des Bildes vollständig transparent ist. Sie können diese Farbe in den beigefügten Bildern sehen, die als Ressourcen in die ausführbare Datei eingebettet werden.
In Zeile 38 geben wir die Typen der wichtigsten Steuerelemente an, und in den Zeilen 46 bis 51 haben wir eine Struktur, die sich in dem Array befindet, das die Steuerelemente enthält. Achten Sie darauf, denn die Fähigkeit, diese Art der Konstruktion zu verstehen, wird für die nächsten Schritte wichtig sein.
Wenn Sie sich den Code der Kontrollklasse ansehen, können Sie feststellen, dass wir nicht mehr alle alten Aufrufe verwenden. In der Tat hat der Code eine ganze Reihe von Änderungen erfahren. In diesem Artikel werde ich nicht auf alle Einzelheiten eingehen, da er noch nicht fertig ist und es uns ermöglicht, die volle Kontrolle über die Situation zu haben. Schauen wir uns nun die SetPlay-Prozedur an, die sich in Zeile 73 befindet. Ich bin mir noch nicht sicher, wie das Verfahren letztendlich heißen wird, aber im Moment ist es das.
In dieser Prozedur wird in Zeile 75 geprüft, ob der Zeiger für die Wiedergabe- und Pausentaste erstellt wurde. Wenn sie noch nicht erstellt wurde, wird der Konstruktor der Zeichenklasse in Zeile 76 ausgeführt. Auf diese Weise legen wir fest, welche Bilder verwendet werden sollen. Genau wie bei der Verwendung der Funktion ObjectCreate aus der MQL5-Bibliothek. In Zeile 77 wird dann das Bild an der von der Schaltfläche angegebenen Position abgespielt und dem Programm mitgeteilt, welches Bild verfolgt werden soll. In Zeile 78 schließlich bitten wir darum, dass das Chart aktualisiert wird, um das Bild zu zeichnen.
Auf die anderen Ereignisse werde ich vorerst nicht eingehen, da wir sie ein anderes Mal betrachten werden. Aber ich möchte, dass Sie der Zeile 188 Ihre Aufmerksamkeit schenken. In dieser Zeile fangen wir die Mausereignisse ab, die MetaTrader 5 verfolgt. Ich werde kurz erklären, was vor sich geht, damit Sie die Grundlagen verstehen können. Später, wenn der Code komplexer wird, werden wir im Detail erklären, was hier eigentlich vor sich geht.
Wenn MetaTrader 5 uns ein Mausereignis sendet, fragen wir den Mausindikator, ob ein Linksklick stattgefunden hat. Dies wird nur bestätigt, wenn sich der Mauszeiger nicht im Studienmodus befindet. Wenn ein Klick erfolgt, prüft die Kontrollklasse, welches Element angeklickt wurde. Es ist ein Fehler aufgetreten, den wir später beheben werden, aber wenn bestätigt wird, dass die Play/Pause-Taste gedrückt wurde, wird der Code in den Zeilen 191-195 ausgeführt, sodass wir eine Vorstellung von der Interaktion zwischen dem Nutzer und dem gesamten System bekommen.
Wie dies geschieht, sehen Sie im folgenden Video. Beachten Sie, dass wir noch nichts Funktionierendes haben, aber die Idee war, zu versuchen, eine solche Interaktion zwischen dem Mauszeiger und dem Kontrollindikator zu ermöglichen.
Schlussfolgerung
In diesem Artikel haben wir uns angesehen, wie man das System ändern kann, um es angenehmer und nützlicher zu machen. Langfristig bedeutet dies, dass wir nicht mehr so oft Programme schreiben müssen und dass das System immer stabiler und zuverlässiger wird. Wir haben jedoch auch gesehen, dass wir in vielen Fällen tiefer in die Verwendung von MQL5 einsteigen müssen, um ein besseres Ergebnis zu erzielen.
Ich habe keine ausführbaren Dateien in die Anwendung aufgenommen, weil sie nicht funktionieren. In der Anlage finden Sie die Bilder, mit denen Sie die alten ersetzen müssen, wenn Sie das gleiche System wie ich entwickeln wollen und dabei den gleichen Code wie in den Artikeln verwenden. Fühlen Sie sich frei zu ändern und zu korrigieren, wie Sie es benötigen.
Im nächsten Artikel werden wir uns diesen Kontrollindikator genauer ansehen. Jetzt wird sich alles exponentiell ausweiten.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/11925
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.