
Entwicklung eines Replay-Systems (Teil 70): Das richtige Bestimmen der Zeit (III)
Einführung
Im vorigen Artikel, „Entwicklung eines Replay-Systems (Teil 69): Das richtige Bestimmen der Zeit (II)“ haben wir gesehen, wie man die verbleibende Zeit eines Balkens auch dann anzeigt, wenn das Symbol eine geringe Liquidität aufweist. Geringe Liquidität bedeutet, dass zu einem bestimmten Zeitpunkt keine Handelsgeschäfte getätigt werden. Dies kann aus verschiedenen Gründen geschehen. Es ist jedoch nicht notwendig, dass wir den genauen Grund für diesen Vorgang erklären. Wir müssen nur einen Weg finden, um mit solchen Situationen angemessen umzugehen.
Es gibt jedoch noch ein Problem, das gelöst werden muss. Dieses Problem ist sozusagen ziemlich lästig und kompliziert, nicht wegen der Programmierung, die mit der Lösung verbunden ist, sondern wegen der Art und Weise, wie wir bestimmen, wann es auftritt und wie wir damit umgehen sollen. Dieses Problem wird als AUKTION bezeichnet.
Im Allgemeinen sind Auktionen das Ergebnis ganz bestimmter Umstände. Sie geschehen nicht willkürlich oder zufällig. Es gibt nämlich sehr klare und strenge Regeln für Auktionen. Aber für uns ist bei der Entwicklung eines Wiedergabe-/Simulationssystems vor allem eines wichtig: Wie kann der Nutzer darüber informiert werden, dass der Vermögenswert an einer Auktion teilgenommen hat? Das ist das wichtigste und einzige Problem, das wir angehen müssen. Glücklicherweise gibt es, wie ich bereits erwähnt habe, die Lösung bereits - sie ist im Mauszeiger implementiert. Wir werden jedoch einige Änderungen am Code vornehmen müssen, um mehr Flexibilität zu erreichen. Damit können wir anzeigen, dass ein nutzerdefinierter Vermögenswert, der in einem Replay oder einer Simulation verwendet wird, an einer Auktion teilgenommen hat.
Nun gut, das ist der einfache Teil. Der schwierige Teil besteht darin, zu bestimmen, wie das Wiedergabe-/Simulatorsystem erklären soll, dass der nutzerdefinierte Vermögenswert den Auktionsstatus erreicht hat. Und das ist wirklich schwierig.
In der Vergangenheit, als sich diese Wiedergabe-/Simulationsanwendung noch im Anfangsstadium der Entwicklung befand, haben wir die folgende Regel verwendet: Wenn zwischen den gehandelten Ticks eine Lücke von 60 Sekunden oder mehr besteht, sollten wir dies als eine Auktion interpretieren. Ich weiß, dass dies nicht die beste Lösung ist, denn es gibt ganz bestimmte Gründe, warum Auktionen stattfinden. Aber ich möchte das System nicht verkomplizieren, indem ich Funktionen hinzufüge, die die Tick-Bewegungen analysieren, um zu erkennen, wann ein Vermögenswert in einen Auktionsstatus eintreten sollte. Dies würde die Komplexität des Systems auf ein Niveau heben, das meiner Meinung nach nicht mehr angemessen und handhabbar ist. Wenn wir das umsetzen, würden wir im Grunde eine Anwendung entwickeln, die in der Lage ist, den Markt zu „erspüren“, um zu erkennen, wenn etwas Ungewöhnliches passiert. Das ist nicht meine Absicht. Natürlich können Sie dieses Wiedergabe-/Simulatorsystem verwenden, um solche Funktionen selbst zu studieren und zu entwickeln, aber ich werde nicht im Detail darauf eingehen, wie man das erreicht.
Deshalb werden wir die ursprüngliche Idee beibehalten, die zu Beginn entwickelt wurde: Wenn der Vermögenswert 60 Sekunden oder länger nicht gehandelt wird, wird der Mauszeiger den Nutzer darüber informieren, dass der Vermögenswert in den Auktionsstatus eingetreten ist. So einfach ist das. Bei diesem Ansatz können wir uns auf den Code konzentrieren. Beginnen wir also mit der Umsetzung.
Lustige Dinge
Es gibt bestimmte Dinge in der Programmierung, die nur diejenigen wirklich verstehen, die tatsächlich Software entwickeln. Ich weiß nicht, wie oder warum sie passieren, und ich versuche auch gar nicht mehr, sie zu verstehen. Nach mehr als zwei Jahrzehnten als professioneller Programmierer habe ich aufgehört, mir über bestimmte Dinge Gedanken zu machen. Ich akzeptiere einfach, dass die Dinge manchmal nicht so laufen, wie sie sollten, und dann gehe ich einfach weiter und akzeptiere, dass ich bestimmte Bedingungen nicht kontrollieren kann. Versuchen Sie also nicht, herauszufinden, was passiert ist. Aktualisieren Sie einfach die Dateien mit dem neuen Code, den ich unten bereitstellen werde. Fragen Sie mich nicht, was passiert ist oder wie der Code funktioniert. Wenn ich versuchen würde, das zu erklären, würden Sie wahrscheinlich denken, ich hätte den Verstand verloren. Der neue Code, der zu verwenden ist, wird im Folgenden vollständig aufgeführt. Es wird keine Anhänge geben. Verwenden Sie einfach diesen neuen Code.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. protected: 011. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 012. //+------------------------------------------------------------------+ 013. struct st_Terminal 014. { 015. ENUM_SYMBOL_CHART_MODE ChartMode; 016. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 017. long ID; 018. string szSymbol; 019. int Width, 020. Height, 021. nDigits, 022. SubWin, 023. HeightBar; 024. double PointPerTick, 025. ValuePerPoint, 026. VolumeMinimal, 027. AdjustToTrade; 028. }; 029. //+------------------------------------------------------------------+ 030. private : 031. st_Terminal m_Infos; 032. struct mem 033. { 034. long Show_Descr, 035. Show_Date; 036. bool AccountLock; 037. }m_Mem; 038. //+------------------------------------------------------------------+ 039. void CurrentSymbol(void) 040. { 041. MqlDateTime mdt1; 042. string sz0, sz1; 043. datetime dt = macroGetDate(TimeCurrent(mdt1)); 044. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 045. 046. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 047. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 048. switch (eTS) 049. { 050. case DOL : 051. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 052. case IND : 053. case WIN : sz1 = "GJMQVZ"; break; 054. default : return; 055. } 056. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 057. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 058. } 059. //+------------------------------------------------------------------+ 060. inline void ChartChange(void) 061. { 062. int x, y, t; 063. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 064. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 065. ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t); 066. ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y); 067. m_Infos.HeightBar = (int)((t - y) / 100); 068. } 069. //+------------------------------------------------------------------+ 070. public : 071. //+------------------------------------------------------------------+ 072. C_Terminal(const long id = 0, const uchar sub = 0) 073. { 074. m_Infos.ID = (id == 0 ? ChartID() : id); 075. m_Mem.AccountLock = false; 076. m_Infos.SubWin = (int) sub; 077. CurrentSymbol(); 078. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 079. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 080. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 081. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 082. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 083. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 084. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 085. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 086. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 087. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 088. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 089. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 090. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 091. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 092. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 093. ChartChange(); 094. } 095. //+------------------------------------------------------------------+ 096. ~C_Terminal() 097. { 098. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 099. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 100. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 101. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 102. } 103. //+------------------------------------------------------------------+ 104. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 105. { 106. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 107. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 108. } 109. //+------------------------------------------------------------------+ 110. inline const st_Terminal GetInfoTerminal(void) const 111. { 112. return m_Infos; 113. } 114. //+------------------------------------------------------------------+ 115. const double AdjustPrice(const double arg) const 116. { 117. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 118. } 119. //+------------------------------------------------------------------+ 120. inline datetime AdjustTime(const datetime arg) 121. { 122. int nSeconds= PeriodSeconds(); 123. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 124. 125. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 126. } 127. //+------------------------------------------------------------------+ 128. inline double FinanceToPoints(const double Finance, const uint Leverage) 129. { 130. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 131. 132. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 133. }; 134. //+------------------------------------------------------------------+ 135. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 136. { 137. static string st_str = ""; 138. 139. switch (id) 140. { 141. case CHARTEVENT_CHART_CHANGE: 142. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 143. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 144. ChartChange(); 145. break; 146. case CHARTEVENT_OBJECT_CLICK: 147. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 148. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 149. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 150. break; 151. case CHARTEVENT_OBJECT_CREATE: 152. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 153. st_str = sparam; 154. break; 155. } 156. } 157. //+------------------------------------------------------------------+ 158. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const 159. { 160. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 161. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 162. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 163. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 164. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 165. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 166. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 167. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 168. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 169. } 170. //+------------------------------------------------------------------+ 171. bool IndicatorCheckPass(const string szShortName) 172. { 173. string szTmp = szShortName + "_TMP"; 174. 175. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 176. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin); 177. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 178. { 179. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 180. Print("Only one instance is allowed..."); 181. SetUserError(C_Terminal::ERR_NoMoreInstance); 182. 183. return false; 184. } 185. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 186. 187. return true; 188. } 189. //+------------------------------------------------------------------+ 190. };
Quellcode von C_Terminal.mqh
Der oben gezeigte Code sollte die alte Datei C_Terminal.mqh ersetzen. Der folgende Code sollte die alte Datei C_Mouse.mqh ersetzen.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_" 007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0)) 008. //+------------------------------------------------------------------+ 009. class C_Mouse : public C_Terminal 010. { 011. public : 012. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 013. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 014. struct st_Mouse 015. { 016. struct st00 017. { 018. short X_Adjusted, 019. Y_Adjusted, 020. X_Graphics, 021. Y_Graphics; 022. double Price; 023. datetime dt; 024. }Position; 025. uchar ButtonStatus; 026. bool ExecStudy; 027. }; 028. //+------------------------------------------------------------------+ 029. protected: 030. //+------------------------------------------------------------------+ 031. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 032. { 033. if (!m_OK) return; 034. CreateObjectGraphics(szName, OBJ_BUTTON); 035. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 036. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 037. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 039. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 046. } 047. //+------------------------------------------------------------------+ 048. private : 049. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 050. struct st01 051. { 052. st_Mouse Data; 053. color corLineH, 054. corTrendP, 055. corTrendN; 056. eStudy Study; 057. }m_Info; 058. struct st_Mem 059. { 060. bool CrossHair, 061. IsFull; 062. datetime dt; 063. string szShortName, 064. szLineH, 065. szLineV, 066. szLineT, 067. szBtnS; 068. }m_Mem; 069. bool m_OK; 070. //+------------------------------------------------------------------+ 071. void GetDimensionText(const string szArg, int &w, int &h) 072. { 073. TextSetFont("Lucida Console", -100, FW_NORMAL); 074. TextGetSize(szArg, w, h); 075. h += 5; 076. w += 5; 077. } 078. //+------------------------------------------------------------------+ 079. void CreateStudy(void) 080. { 081. if (m_Mem.IsFull) 082. { 083. CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH); 084. CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH); 085. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2); 086. CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy); 087. } 088. m_Info.Study = eStudyCreate; 089. } 090. //+------------------------------------------------------------------+ 091. void ExecuteStudy(const double memPrice) 092. { 093. double v1 = GetInfoMouse().Position.Price - memPrice; 094. int w, h; 095. 096. if (!CheckClick(eClickLeft)) 097. { 098. m_Info.Study = eStudyNull; 099. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 100. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 101. }else if (m_Mem.IsFull) 102. { 103. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 104. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 105. GetDimensionText(sz1, w, h); 106. ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1); 107. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 108. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w); 109. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h); 110. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 112. ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 114. } 115. m_Info.Data.ButtonStatus = eKeyNull; 116. } 117. //+------------------------------------------------------------------+ 118. inline void DecodeAlls(int xi, int yi) 119. { 120. int w = 0; 121. 122. xi = (xi > 0 ? xi : 0); 123. yi = (yi > 0 ? yi : 0); 124. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 125. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 126. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 127. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 128. yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin); 129. m_Info.Data.Position.X_Adjusted = (short) xi; 130. m_Info.Data.Position.Y_Adjusted = (short) yi; 131. } 132. //+------------------------------------------------------------------+ 133. public : 134. //+------------------------------------------------------------------+ 135. C_Mouse(const long id, const string szShortName) 136. :C_Terminal(id), 137. m_OK(false) 138. { 139. m_Mem.szShortName = szShortName; 140. } 141. //+------------------------------------------------------------------+ 142. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 143. :C_Terminal(id) 144. { 145. if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) return; 146. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 147. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 148. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 149. ZeroMemory(m_Info); 150. m_Info.corLineH = corH; 151. m_Info.corTrendP = corP; 152. m_Info.corTrendN = corN; 153. m_Info.Study = eStudyNull; 154. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 155. CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH); 156. ChartRedraw(GetInfoTerminal().ID); 157. } 158. //+------------------------------------------------------------------+ 159. ~C_Mouse() 160. { 161. if (!m_OK) return; 162. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 163. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1); 164. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 165. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 166. } 167. //+------------------------------------------------------------------+ 168. inline bool CheckClick(const eBtnMouse value) 169. { 170. return (GetInfoMouse().ButtonStatus & value) == value; 171. } 172. //+------------------------------------------------------------------+ 173. inline const st_Mouse GetInfoMouse(void) 174. { 175. if (!m_OK) 176. { 177. double Buff[]; 178. uCast_Double loc; 179. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 180. 181. ZeroMemory(m_Info.Data); 182. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 183. { 184. loc.dValue = Buff[0]; 185. m_Info.Data.ButtonStatus = loc._8b[0]; 186. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 187. } 188. IndicatorRelease(handle); 189. } 190. 191. return m_Info.Data; 192. } 193. //+------------------------------------------------------------------+ 194. inline void SetBuffer(const int rates_total, double &Buff[]) 195. { 196. uCast_Double info; 197. 198. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 199. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 200. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 201. Buff[rates_total - 1] = info.dValue; 202. } 203. //+------------------------------------------------------------------+ 204. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 205. { 206. int w = 0; 207. static double memPrice = 0; 208. 209. if (m_OK) 210. { 211. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 212. switch (id) 213. { 214. case (CHARTEVENT_CUSTOM + evHideMouse): 215. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE); 216. break; 217. case (CHARTEVENT_CUSTOM + evShowMouse): 218. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH); 219. break; 220. case CHARTEVENT_MOUSE_MOVE: 221. DecodeAlls((int)lparam, (int)dparam); 222. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price); 223. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0); 224. m_Info.Data.ButtonStatus = (uchar) sparam; 225. if (CheckClick(eClickMiddle)) 226. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 227. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 228. { 229. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 230. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 231. m_Info.Study = eStudyExecute; 232. } 233. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 234. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 235. break; 236. case CHARTEVENT_OBJECT_DELETE: 237. if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH)) 238. CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH); 239. break; 240. } 241. } 242. } 243. //+------------------------------------------------------------------+ 244. }; 245. //+------------------------------------------------------------------+ 246. #undef macro_NameObjectStudy 247. //+------------------------------------------------------------------+
Quellcode der Datei C_Mouse.mqh
Nachdem wir diese Änderungen vorgenommen haben, wollen wir sehen, was wir als Nächstes tun müssen. Denn, so seltsam es auch klingen mag, manchmal passieren unerwartete Dinge. Und Sie als angehender Programmierer müssen verstehen, dass Sie manchmal einfach nicht in der Lage sein werden, herauszufinden, was vor sich geht. Denn es passieren tatsächlich von Zeit zu Zeit seltsame Dinge. Deshalb zeige ich Ihnen jede der Veränderungen, die vorgenommen werden, damit Sie selbst sehen können, dass nicht alles in Ihrer Hand liegt.
Ein neuer Maus-Indikator taucht auf
In diesem Stadium müssen wir eine für viele Entwickler eher ungewöhnliche Änderung vornehmen. Wir müssen den Mausindikator in die Lage versetzen, alle Ereignisse zu verarbeiten, die auf einem realen Markt auftreten würden, aber im Rahmen der Wiedergabe/Simulation. Sie denken jetzt vielleicht: „Aber tut es das nicht schon?“ Nein. Leider hatte ich bisher das Wiedergabe-/Simulationssystem von dem System getrennt, das eine Verbindung zum realen Server herstellt, sei es auf einem Demokonto oder einem Live-Konto. Daher gab es zwei Betriebsmodi für den Mauszeiger: einen, bei dem er Ereignisse des Order Books verwendete, und einen anderen, bei dem diese Ereignisse ignoriert wurden.
Das große Thema hier sind die Ereignisse im Order Book. Diese Ereignisse sind jedoch etwas komplexer, als Sie vielleicht denken. Es geht nicht nur darum, die Funktion CustomBookAdd aufzurufen, die Teil der MQL5-Standardbibliothek ist, und zu erwarten, dass alles funktioniert. Für uns, die wir in der Wiedergabe-/Simulationsumgebung arbeiten, ist die Situation ein wenig komplizierter. Aber es gibt keinen Grund zur Sorge. Wir werden sicher zu dem Punkt kommen, an dem ich erkläre, wie man die Funktion CustomBookAdd verwendet. Lassen Sie uns ohne Eile vorgehen. Lassen Sie uns einen Schritt nach dem anderen machen.
Wie Sie vielleicht im vorherigen Abschnitt bemerkt haben, wurden sowohl der Quellcode der Header-Datei C_Terminal.mqh als auch die Datei C_Mouse.mqh geändert. Auch wenn ich nicht auf die Einzelheiten dieser Änderungen eingegangen bin, sollten Sie wissen, dass Änderungen vorgenommen wurden. Da sie jedoch relativ einfach zu verstehen sind, sehe ich keine Notwendigkeit, sie im Detail zu erläutern. So wie in diesen Dateien Änderungen vorgenommen wurden, gibt es auch Änderungen in der Header-Datei C_Study.mqh. Der vollständige Code für diese Datei ist unten zu finden.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. //+------------------------------------------------------------------+ 008. class C_Study : public C_Mouse 009. { 010. private : 011. //+------------------------------------------------------------------+ 012. struct st00 013. { 014. eStatusMarket Status; 015. MqlRates Rate; 016. string szInfo, 017. szBtn1, 018. szBtn2, 019. szBtn3; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. }m_Info; 025. //+------------------------------------------------------------------+ 026. void Draw(void) 027. { 028. double v1; 029. 030. if (m_Info.bvT) 031. { 032. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 033. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo); 034. } 035. if (m_Info.bvD) 036. { 037. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 038. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 039. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 040. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 041. } 042. if (m_Info.bvP) 043. { 044. v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 045. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 047. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 048. } 049. } 050. //+------------------------------------------------------------------+ 051. inline void CreateObjInfo(EnumEvents arg) 052. { 053. switch (arg) 054. { 055. case evShowBarTime: 056. C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise); 057. m_Info.bvT = true; 058. break; 059. case evShowDailyVar: 060. C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 061. m_Info.bvD = true; 062. break; 063. case evShowPriceVar: 064. C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 065. m_Info.bvP = true; 066. break; 067. } 068. } 069. //+------------------------------------------------------------------+ 070. inline void RemoveObjInfo(EnumEvents arg) 071. { 072. string sz; 073. 074. switch (arg) 075. { 076. case evHideBarTime: 077. sz = m_Info.szBtn1; 078. m_Info.bvT = false; 079. break; 080. case evHideDailyVar: 081. sz = m_Info.szBtn2; 082. m_Info.bvD = false; 083. break; 084. case evHidePriceVar: 085. sz = m_Info.szBtn3; 086. m_Info.bvP = false; 087. break; 088. } 089. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 090. ObjectDelete(GetInfoTerminal().ID, sz); 091. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 092. } 093. //+------------------------------------------------------------------+ 094. public : 095. //+------------------------------------------------------------------+ 096. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 097. :C_Mouse(IdParam, szShortName, corH, corP, corN) 098. { 099. if (_LastError >= ERR_USER_ERROR_FIRST) return; 100. ZeroMemory(m_Info); 101. m_Info.corP = corP; 102. m_Info.corN = corN; 103. CreateObjInfo(evShowBarTime); 104. CreateObjInfo(evShowDailyVar); 105. CreateObjInfo(evShowPriceVar); 106. ResetLastError(); 107. } 108. //+------------------------------------------------------------------+ 109. void Update(const eStatusMarket arg) 110. { 111. int i0; 112. datetime dt; 113. 114. if (m_Info.Rate.close == 0) 115. m_Info.Rate.close = iClose(NULL, PERIOD_D1, ((_Symbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(NULL, PERIOD_D1, 0))) ? 0 : 1)); 116. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 117. { 118. case eCloseMarket : 119. m_Info.szInfo = "Closed Market"; 120. break; 121. case eInReplay : 122. case eInTrading : 123. i0 = PeriodSeconds(); 124. dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent()); 125. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 126. if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 127. break; 128. case eAuction : 129. m_Info.szInfo = "Auction"; 130. break; 131. default : 132. m_Info.szInfo = "ERROR"; 133. } 134. Draw(); 135. } 136. //+------------------------------------------------------------------+ 137. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 138. { 139. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 140. switch (id) 141. { 142. case CHARTEVENT_CUSTOM + evHideBarTime: 143. RemoveObjInfo(evHideBarTime); 144. break; 145. case CHARTEVENT_CUSTOM + evShowBarTime: 146. CreateObjInfo(evShowBarTime); 147. break; 148. case CHARTEVENT_CUSTOM + evHideDailyVar: 149. RemoveObjInfo(evHideDailyVar); 150. break; 151. case CHARTEVENT_CUSTOM + evShowDailyVar: 152. CreateObjInfo(evShowDailyVar); 153. break; 154. case CHARTEVENT_CUSTOM + evHidePriceVar: 155. RemoveObjInfo(evHidePriceVar); 156. break; 157. case CHARTEVENT_CUSTOM + evShowPriceVar: 158. CreateObjInfo(evShowPriceVar); 159. break; 160. case CHARTEVENT_MOUSE_MOVE: 161. Draw(); 162. break; 163. } 164. ChartRedraw(GetInfoTerminal().ID); 165. } 166. //+------------------------------------------------------------------+ 167. }; 168. //+------------------------------------------------------------------+ 169. #undef def_ExpansionPrefix 170. #undef def_MousePrefixName 171. //+------------------------------------------------------------------+
Der Quellcode der Datei C_Study.mqh
Im Gegensatz zu den anderen Header-Dateien, die im vorigen Abschnitt besprochen wurden, hat der Code, der hier implementiert wird, mehr Gewicht, und die Situation ist etwas anders.
Beachten Sie, dass der Konstruktor der Klasse C_Study einer Codebereinigung unterzogen wurde. In Zeile 99 haben wir den Testwert geändert, um zu verhindern, dass Fehler, die nicht vom Code herrühren, die Ausführung vorzeitig beenden. Es gibt jedoch noch einen weiteren Punkt, der erwähnenswert ist. Beachten Sie, dass wir Rate.close nicht mehr im Konstruktor initialisieren. Diese Initialisierung wird nun an einer anderen Stelle im Code durchgeführt. In Zeile 114 wird geprüft, ob der Wert Null ist. Wenn das der Fall ist, erfassen wir in Zeile 115 den Schlusskurs, so wie wir es zuvor getan haben.
Diese Änderung wurde vorgenommen, weil es vorkam, dass Rate.close einen falschen Wert erhielt, wenn der Indikator in einer Vorlage platziert und dann zum Chart hinzugefügt wurde, wenn wir ihn im Konstruktor initialisierten. Ehrlich gesagt, konnte ich nicht ganz verstehen, warum dies geschah. Tatsache ist jedoch, dass nach den Änderungen am Code die Initialisierung von Rate.close zufällig und unregelmäßig erfolgt. Um die Wahrscheinlichkeit und die Auswirkungen dieses Problems zu verringern, habe ich den Initialisierungspunkt verschoben.
Nun gibt es ein wichtiges Detail: Die Prüfung in Zeile 114 wird immer true zurückgeben, wenn wir Zeile 100 ausführen, weil Zeile 100 alle Werte in der m_Info-Struktur auf Null zurücksetzt. Aber auch wenn der gesamte Code vollständig dargestellt wird, waren dies die Punkte, die meiner Meinung nach eine Erklärung verdienen. Der Rest des Codes blieb praktisch unverändert. Na gut. Kommen wir nun zu den Änderungen, die am Code des Mauszeigers vorgenommen wurden. Der vollständige Code ist unten dargestellt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.70" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/ru/articles/12326" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. double GL_PriceClose; 14. datetime GL_TimeAdjust; 15. //+------------------------------------------------------------------+ 16. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 17. //+------------------------------------------------------------------+ 18. C_Study *Study = NULL; 19. //+------------------------------------------------------------------+ 20. input color user02 = clrBlack; //Price Line 21. input color user03 = clrPaleGreen; //Positive Study 22. input color user04 = clrLightCoral; //Negative Study 23. //+------------------------------------------------------------------+ 24. C_Study::eStatusMarket m_Status; 25. int m_posBuff = 0; 26. double m_Buff[]; 27. //+------------------------------------------------------------------+ 28. int OnInit() 29. { 30. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 32. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 33. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 34. m_Status = C_Study::eCloseMarket; 35. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 36. ArrayInitialize(m_Buff, EMPTY_VALUE); 37. 38. return INIT_SUCCEEDED; 39. } 40. //+------------------------------------------------------------------+ 41. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 42. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 43. const long& volume[], const int& spread[]) 44. { 45. GL_PriceClose = close[rates_total - 1]; 46. if (_Symbol == def_SymbolReplay) 47. GL_TimeAdjust = spread[rates_total - 1] & (~def_MaskTimeService); 48. m_posBuff = rates_total; 49. (*Study).Update(m_Status); 50. 51. return rates_total; 52. } 53. //+------------------------------------------------------------------+ 54. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 55. { 56. (*Study).DispatchMessage(id, lparam, dparam, sparam); 57. (*Study).SetBuffer(m_posBuff, m_Buff); 58. 59. ChartRedraw((*Study).GetInfoTerminal().ID); 60. } 61. //+------------------------------------------------------------------+ 62. void OnBookEvent(const string &symbol) 63. { 64. MqlBookInfo book[]; 65. C_Study::eStatusMarket loc = m_Status; 66. 67. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 68. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 69. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : (symbol == def_SymbolReplay ? C_Study::eInReplay : C_Study::eInTrading)); 70. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 71. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 72. if (loc != m_Status) (*Study).Update(m_Status); 73. } 74. //+------------------------------------------------------------------+ 75. void OnDeinit(const int reason) 76. { 77. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 78. 79. delete Study; 80. } 81. //+------------------------------------------------------------------+
Der Quellcode des Mauszeigers
Wie Sie sehen können, hat sich der Code des Mauszeigers nun stark verändert. Eigentlich ist er ganz anders als früher. Diese Änderungen wurden aus einem bestimmten Grund vorgenommen. Auch wenn Sie sie nicht ganz verstehen können, geht es darum, den Mausindikator allgemeiner zu gestalten und gleichzeitig die Konsistenz bei allen Arten von Charts oder Instrumenten zu wahren. Schauen wir uns die Änderungen an.
Zuerst haben wir die Initialisierungsfunktion OnInit. Wenn Sie genau hinsehen, werden Sie feststellen, dass sie sich von der vorherigen Version unterscheidet. Hier müssen wir einige Details beachten. Die erste ist, dass wir in Zeile 31 prüfen, ob eine Fehlervariable durch einen Aufruf, den wir gemacht haben, gesetzt wurde. Zuvor hatte jeder Fehler dazu geführt, dass die Initialisierung INIT_FAILED meldete. Warum also habe ich das geändert? Der Grund dafür ist, dass der Indikator bei der Initialisierung manchmal einen Fehler meldete, der keinen Sinn ergab. Da der Klassenkonstruktor jedoch keine Rückgabe eines Wertes zulässt, habe ich die Variable _LastError für diesen Zweck verwendet. Dies erwies sich jedoch als nicht sehr praktisch. Also beschloss ich, denselben Ansatz beizubehalten, aber die Fehler mit SetUserError auf etwas Spezifischeres zu filtern. Diese Art von Fehlern tritt nicht häufig auf, aber es ist wichtig für uns zu überprüfen, ob die Konstrukteure ihre Arbeit richtig gemacht haben.
Danach führen wir etwas ein, das es vorher nicht gab: Wir teilen MetaTrader 5 mit, dass wir Ereignisse des Order Books empfangen wollen. Wichtig: In der Vergangenheit waren Ereignisse des Order Books nur verfügbar, wenn die Chartdaten zu einem echten Instrument gehörten, d.h. wenn eine Verbindung zum Handelsserver bestand. Jetzt erhalten wir stets und ständig die Ereignisse des Order Books, auch wenn es sich um ein individuelles Instrument handelt. Dies ist wichtig. Ich beabsichtige, den gleichen Mechanismus zu verwenden, um den Nutzer zu informieren, wenn sich das Instrument im Auktionsmodus befindet. Dies vereinfacht die Arbeit erheblich, da die Schnittstelle gleich bleibt und keine besonderen Anpassungen erforderlich sind. Wir müssen nur die richtigen Daten an den Indikator übergeben, den Rest erledigt er.
Beachten Sie, dass wir in Zeile 34 mit dem Wert beginnen, der anzeigt, ob der Markt geschlossen ist. In den Zeilen 35 und 36 wird dann der Datenpuffer des Mauszeigers initialisiert. Es ist ein einfaches Verfahren, das schon einmal durchgeführt wurde. Der Unterschied besteht darin, dass wir MetaTrader 5 jetzt ausdrücklich anweisen, immer auf Ereignisse des Order Books zu achten, unabhängig vom Instrument.
Überspringen wir OnCalculate vorerst und kommen später darauf zurück. Sie haben vielleicht bemerkt, dass iSpread nicht mehr aufgerufen wird. Gehen wir stattdessen zu Zeile 75, wo wir die Funktion OnDeInit finden. Da wir Ereignisse des Order Books für jedes Instrument empfangen werden, müssen wir MetaTrader 5 mitteilen, dass wir sie ab einem bestimmten Zeitpunkt nicht mehr empfangen möchten. Dies geschieht in Zeile 77. Und da wir den Indikator aus dem Chart entfernen, verwenden wir in Zeile 79 den Löschoperator, um den Speicher an das System zurückzugeben.
Schauen wir uns nun die Ereignisbehandlung von Order Book an. Es ist in Zeile 62, in der Funktion OnBookEvent. Dies soll so einfach wie möglich sein. Wir sind nicht daran interessiert, das Volumen oder die Anzahl der Aufträge im Order Book zu analysieren. Für uns ist es wichtig zu wissen, ob sich das Instrument im Handels- oder im Auktionsmodus befindet und ob der Markt geschlossen ist. Um dies festzustellen, müssen wir nur beobachten, was im Order Book passiert.
In Zeile 67 filtern wir die Aufrufe auf der Grundlage des Instruments, an das der Indikator gebunden ist. Das liegt daran, dass MetaTrader 5 die Ereignisse des Order Books nicht nach Instrumenten filtert, sondern sie einfach an alle Charts abfeuert, die sie angefordert haben. Deshalb ist es keine gute Idee, mehrere Oder Books mit den Instrumenten zu lesen, die uns nicht interessieren. Dies würde zu unnötigen Ereignissen führen und Zeit kosten. Nach der Filterung werden in Zeile 68 die neuesten Daten des Order Books über einen Aufruf der MQL5-Bibliothek erfasst. Dies ist notwendig, damit dieses Verfahren seine Aufgabe erfüllen kann.
Hier kommt der interessanteste und nützlichste Teil von OnBookEvent. Achten Sie genau auf Zeile 69. Wenn das Datenarray des Oder Books leer ist, bedeutet dies, dass der Markt geschlossen ist. Wenn es Daten gibt, brauchen wir eine neue Prüfung. Warum? Ganz einfach: Wir müssen wissen, woher die Daten stammen, die wir analysieren. Sie können dies in der Header-Datei C_Study.mqh sehen, die weiter oben gezeigt wurde. Dies gilt insbesondere für das Aktualisierungsverfahren. In Zeile 124 rufen wir TimeCurrent auf, wenn das Instrument mit dem Handelsserver verbunden ist, oder wir verwenden eine globale Variable, wenn es sich um ein nutzerdefiniertes Instrument im Wiedergabe-/Simulationsmodus handelt. Aus diesem Grund ist der Test in Zeile 69 notwendig. Ohne sie würden wir riskieren, falsche Daten zu verwenden, was alles durcheinander bringen würde. Nachdem wir den Primärstatus des Symbols bestimmt haben, müssen wir seinen tatsächlichen Zustand überprüfen. Dies geschieht in Zeile 70, wo wir das Order Book nach einer bestimmten Art von Information durchsuchen. In diesem Fall prüfen wir das Vorhandensein von BOOK_TYPE_BUY_MARKET oder BOOK_TYPE_SELL_MARKET. Ist beides der Fall, bedeutet dies, dass der Markt offen ist, das Instrument sich aber im Auktionsmodus befindet.
Beachten Sie, wie ein einfacher Schritt in OnInit, der Ereignisse des Order Books für jedes Instrument aktiviert, es ermöglicht, den aktuellen Status des Instruments zu bestimmen. Diese Art von Umsetzungsdetails ist es, die diese Arbeit so interessant macht.
Wenden wir uns nun wieder der Zeile 41 zu, der Prozedur OnCalculate. Das fragen Sie sich wahrscheinlich: Warum wurde dieser Code erneut geändert? Hat MetaTrader 5 zwischen dem letzten Artikel und diesem ein Update erhalten? Nun, eigentlich nicht. Bedenken Sie, lieber Leser, dass diese Artikel vor langer Zeit geschrieben wurden. Aber der Inhalt wird erst jetzt veröffentlicht.
Das Ergebnis dieser Ausführung ist in dem folgenden Video zu sehen, in dem ich demonstriere, was tatsächlich passiert. Bevor Sie die Erklärung lesen, sehen Sie sich das Video an. Es wird Ihnen helfen, das Folgende zu verstehen.
Demo-Video
Wenn Sie sich das Video angesehen haben, ist Ihnen wahrscheinlich aufgefallen, dass ich die Dinge auf eine ganz bestimmte Art und Weise mache. Kümmern Sie sich vorerst nicht um den Code für den Dienst, das wird zu einem anderen Zeitpunkt behandelt. Konzentrieren wir uns hier zunächst auf den Mauszeiger.
Zunächst habe ich den Dienst initialisiert. Es blieb in einem Wartezustand, um zu erkennen, wann der Mausindikator dem Chart hinzugefügt werden würde. Sobald er platziert war, konnte man beobachten, dass auf dem Display ein Countdown angezeigt wurde und die Werte auch in der MetaTrader 5 Toolbox erschienen. So konnten wir überprüfen, ob der Mauszeiger tatsächlich auf die vom Dienst bereitgestellten Werte zugreifen konnte.
Beachten Sie nun den folgenden Punkt: Im vorigen Artikel hat der Dienst an einer Stelle weiterhin Daten an den Mauszeiger gesendet. Der Indikator war jedoch nicht in der Lage, uns eine zeitliche Aktualisierung auf der Grundlage der Werte zu liefern, die er vom Dienst erhielt. Das lag daran, dass das Display einfach eingefroren war. Beobachten Sie nun, dass das Programm einfriert, wenn wir denselben Code wie jetzt in der Funktion OnCalculate verwenden. Um das Einfrieren zu verhindern, haben wir damals auf den Aufruf iSpread zurückgegriffen. In diesem Fall verwenden wir diesen Aufruf jedoch nicht, und dennoch blieb die Anzeige nicht stehen. Warum hat es jetzt funktioniert, aber nicht im vorherigen Artikel? Ehrlich gesagt, weiß ich die Antwort auf diese Frage nicht. Es ist eines der Dinge, die nur diejenigen, die wirklich experimentieren und die Grenzen ausloten, aus erster Hand erfahren können.
Ich teile dies mit Ihnen, lieber Leser, um Ihnen zu zeigen, dass wir als Programmierer nicht immer auf alles eine Antwort haben. Es gibt Dinge, die uns begegnen, aber wir wissen nicht, warum oder wie wir zu diesem Ergebnis gekommen sind. Meiner Meinung nach macht es sowohl den Lernprozess als auch meine eigene Erfahrung viel angenehmer, wenn ich diese Art von Situation mit Ihnen teilen kann. Denn Sie werden lernen, dass nicht alle Ziele von Anfang an erreicht werden. Und für mich lerne ich, wie jedes der kleinsten Teile von MetaTrader 5 funktioniert und wie weit ich es treiben kann, ohne einen kompletten Systemabsturz zu verursachen. Um diesen Artikel abzuschließen, werfen wir einen Blick auf den Quellcode des im Video verwendeten Dienstes.
Test-Dienst
Normalerweise fällt es den Menschen schwer, das Programmieren zu lernen, weil sie das nicht verstehen:
DIE REIHENFOLGE DER EREIGNISSE BEEINFLUSST DAS ERGEBNIS
Aber wenn es um die Programmierung geht, ist das in den meisten Fällen der Fall. Selten können wir etwas programmieren, ohne uns Gedanken über die Reihenfolge der Ereignisse zu machen. Aber hier, im Test-Dienst, ist die obige Aussage absolut wahr. Werfen wir also einen Blick auf den Quellcode des Dienstes.
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. //+------------------------------------------------------------------+ 06. #include <Market Replay\Defines.mqh> 07. #include <Market Replay\Auxiliar\Macros.mqh> 08. //+------------------------------------------------------------------+ 09. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 10. //+------------------------------------------------------------------+ 11. void OnStart() 12. { 13. long id; 14. int time; 15. MqlRates Rate[1]; 16. MqlBookInfo book[1]; 17. 18. Print("Starting Test Service..."); 19. SymbolSelect(def_SymbolReplay, false); 20. CustomSymbolDelete(def_SymbolReplay); 21. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 22. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5); 23. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5); 24. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1); 25. Rate[0].close = 105; 26. Rate[0].open = 100; 27. Rate[0].high = 110; 28. Rate[0].low = 95; 29. Rate[0].tick_volume = 5; 30. Rate[0].spread = 1; 31. Rate[0].real_volume = 10; 32. Rate[0].time = D'14.03.2023 08:30'; 33. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 34. Rate[0].time = D'14.03.2023 09:00'; 35. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 36. SymbolSelect(def_SymbolReplay, true); 37. id = ChartOpen(def_SymbolReplay, PERIOD_M30); 38. 39. Sleep(1000); 40. 41. Print("Waiting for Mouse Indicator..."); 42. while ((def_Loop) && (ChartIndicatorGet(id, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 43. 44. book[0].type = BOOK_TYPE_BUY; 45. book[0].price = Rate[0].close; 46. book[0].volume = 1; 47. CustomBookAdd(def_SymbolReplay, book, 1); 48. 49. Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS)); 50. time = (int)macroGetTime(Rate[0].time); 51. while (def_Loop) 52. { 53. Rate[0].spread = (int)(def_MaskTimeService | time); 54. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 55. Sleep(250); 56. time++; 57. Print(TimeToString(time, TIME_SECONDS), " >> (int):", time); 58. switch (time) 59. { 60. case 32430: 61. book[0].type = BOOK_TYPE_BUY_MARKET; 62. CustomBookAdd(def_SymbolReplay, book, 1); 63. break; 64. case 32460: 65. book[0].type = BOOK_TYPE_BUY; 66. CustomBookAdd(def_SymbolReplay, book, 1); 67. break; 68. } 69. } 70. ChartClose(id); 71. SymbolSelect(def_SymbolReplay, false); 72. CustomSymbolDelete(def_SymbolReplay); 73. Print("Finished Test Service..."); 74. } 75. //+------------------------------------------------------------------+
Quellcode des Test-Dienstes
Beachten Sie, dass der Code praktisch derselbe ist wie im vorhergehenden Artikel, der zum Testen des Mauszeigers diente. Allerdings gibt es hier einige neue Elemente. Dies sind die Elemente, die den gesamten Prozess ausgelöst haben, den Sie im Video gesehen haben. Jede neue Zeile hier hat einen Grund und eine Bedeutung. Die meisten Menschen, die versuchen, das Order Book für ein nutzerdefiniertes Symbol zu verwenden, scheitern, weil sie nicht verstehen, was wirklich getan werden muss. Der Grund dafür ist, dass sie sich ausschließlich auf den Aufruf von CustomBookAdd konzentrieren, der in den Zeilen 47, 62 und 64 zu sehen ist. Aber das ist nicht die Art, wie die Dinge funktionieren. Der Versuch, Daten über ein nutzerdefiniertes Symbol an das Oder Book zu übergeben, indem man nur den Bibliotheksaufruf CustomBookAdd verwendet, wird absolut nicht funktionieren, es sei denn, man unternimmt vorher ein paar Schritte. Und um Fehler zu vermeiden, müssen diese Schritte sorgfältig geplant werden.
Der wichtigste Schritt passiert in Zeile 24. Ohne sie ist die Funktion CustomBookAdd für uns völlig nutzlos. Aber ist CustomBookAdd nicht speziell dafür gedacht, Daten an das Oder Book zu senden? Genau wie die Funktionen für Tick und Rate? Die Antwort auf all diese Fragen lautet sowohl ja als auch nein. Es klingt widersprüchlich, aber die Funktion CustomBookAdd hat nur dann eine Wirkung, wenn Sie zuerst einen Wert für SYMBOL_TICKS_BOOKDEPTH definieren. Welcher Wert für SYMBOL_TICKS_BOOKDEPTH zu verwenden ist, hängt davon ab, was wir vorhaben. Da unser Ziel nur darin besteht, den Mauszeiger so zu führen, dass er den Nutzer über den Status des nutzerdefinierten Symbols informiert, benötigen wir nur eine einzige Ebene. Aus diesem Grund habe ich in dem Aufruf in Zeile 24 zur Definition von SYMBOL_TICKS_BOOKDEPTH den Wert 1 verwendet. Wenn Sie jedoch ein künstliches Oder Book für Simulations- oder Wiederholungsstudien erstellen möchten, müssen Sie nur den Wert von eins auf einen Wert ändern, der Ihren Anforderungen entspricht.
Auf jeden Fall ist dies der grundlegende Teil. Erst nach der Definition von SYMBOL_TICKS_BOOKDEPTH können wir tatsächlich Daten in der Struktur MqlBookInfo empfangen. Dieser Aspekt war für mich etwas schwierig zu verstehen, da ich keine Referenz finden konnte, die erklärt, wie man die MqlBookInfo-Struktur für ein nutzerdefiniertes Symbol ausfüllt. In allen Verweisen wurde lediglich die Verwendung von CustomBookAdd empfohlen. In der Dokumentation wird jedoch nur SYMBOL_TICKS_BOOKDEPTH erwähnt. Es wurde keine Verbindung zwischen dieser Definition und der Möglichkeit des Zugriffs auf die Daten in der von CustomBookAdd gebuchten MqlBookInfo-Struktur hergestellt.
In jedem Fall wird dieser Dienst ausschließlich zu Testzwecken für die Datenübertragung genutzt. Es wird keinen anderen Zweck erfüllen. Doch bevor ich diesen Artikel abschließe, möchte ich kurz erklären, was hier passiert. Zwischen den Zeilen 44 und 46 definieren wir eine Ebene des Oder Books. Der für uns wichtigste Teil ist Zeile 44. In Zeile 47 teilen wir MetaTrader 5 mit, dass er ein Ereignis erzeugen und die Daten als Oder Book zur Verfügung stellen soll. Achten Sie jetzt genau darauf: In Zeile 50 wird die Uhrzeit erfasst. In Zeile 56 wird die Zeit um eine Sekunde erhöht. Wie in Zeile 34 definiert, beträgt der Anfangswert der Zeitvariablen 32400. Die Angabe erfolgt in Sekunden. Vergessen Sie diesen Punkt nicht. In Zeile 58 führen wir also eine Überprüfung durch, um unsere aktuelle Position zu bestimmen. Nach 30 Sekunden senden wir einen neuen Wert an das Oder Book in Zeile 62. Der zu sendende Wert wird in Zeile 61 angezeigt. Dieser Wert bewirkt, dass die Mausanzeige anzeigt, dass sich das Symbol im Auktionsmodus befindet. Nach weiteren 30 Sekunden veranlasst die Prüfung die Ausführung von Zeile 64, was bedeutet, dass der an das Order Book zu sendende Wert aus Zeile 65 über die Ausführung von Zeile 66 stammt. Dadurch wird die Zeitanzeige auf dem Mauszeiger wieder eingeblendet.
Ein letztes Detail: Die Zeit entspricht nicht genau einer Sekunde, da wir in Zeile 55 eine Verzögerung von einer Viertelsekunde einführen.
Abschließende Überlegungen
In diesem Artikel habe ich Ihnen gezeigt, wie Sie Werte für ein nutzerdefiniertes Symbol an das Order Book übergeben können. Obwohl der einzige Zweck des von uns genutzten Dienstes darin besteht, diese Art der Übergabe von Werten zu testen, haben wir gezeigt, dass wir dies auf völlig vernünftige Weise tun können. Trotz der anfänglichen Herausforderungen ist es tatsächlich möglich. Somit haben Sie nun eine weitere Möglichkeit, Informationen zwischen einem Service und einem Indikator oder Expert Advisor über das Order Book zu übertragen. Sie sollten jedoch immer bedenken, dass alles seinen Preis hat. Und die wahllose Nutzung des Order Books hat ihren Preis, der berücksichtigt werden muss.
Auf jeden Fall spiegelt dieser Test-Dienst nicht wider, was wir im nächsten Artikel tun werden. Wir werden einen Wiedergabe/Simulator einsetzen, um uns zu informieren, ob das Symbol im Auktionsmodus ist oder nicht. Ist dieses System nicht interessant? Also, wir sehen uns im nächsten Artikel wieder.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12326





- 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.