
Developing a Replay System (Part 70): Getting the Time Right (III)
Introduction
In the previous article, "Developing a Replay System (Part 69): Getting the Time Right (II)", we have seen how to display the remaining time of a bar even at times when the symbol has low liquidity. Low liquidity refers to the absence of trades being executed at a given moment. This can happen for various reasons. However, it's not necessary for us to explain the specific reason why this occurs. All we need to do is find a way to handle such situations appropriately.
However, we still face a problem that needs to be solved. This problem is, so to speak, rather annoying and complicated, not because of the programming involved in the solution, but because of how we determine when it occurs and how we should handle it. This problem is known as the AUCTION.
In general, auctions result from very specific circumstances. They do not happen arbitrarily or randomly. In fact, there are very clear and strict rules governing auctions. But for us, in the development of a replay/simulation system, what truly matters is this: How can we inform the user that the asset has entered an auction? This is the main and only issue we need to address. Fortunately, as I mentioned earlier, the solution already exists - it's implemented in the mouse indicator. However, we will need to make a few changes to the code to give us more flexibility. This will allow us to indicate that a custom asset, being used in a replay or simulation, has entered an auction.
Very well, this is the easy part The difficult part is determining how the replay/simulator system should declare that the custom asset has entered auction status. And it's really difficult.
In the past, when this replay/simulation application was still in the early stages of development, we used the following rule: If there is a gap of 60 seconds or more between traded ticks, we should interpret this as an auction. I know this is not the best solution, as there are very specific reasons why auctions occur. But I don't want to complicate the system by adding functionality that would analyze tick movements to detect when an asset should enter an auction state. Doing so would essentially take the system complexity to a level beyond what I believe is reasonable and manageable. If we implemented that, we'd essentially be building an application capable of "sensing" the market to detect when something unusual is happening. That's not my intention. Of course, you can use this replay/simulator system to study and develop such functionality on your own, but I won't go into details about how to achieve that.
Therefore, we'll keep the original idea developed early on: If the asset goes 60 seconds or more without a trade occurring, we will make the mouse indicator notify the user that the asset has entered auction status. Simple as that. This approach lets us focus on the code. So, let's start with the implementation.
Funny Things
There are certain things in programming that only those who actually develop software will truly understand. I don't know how or why they happen, and I don't even try to understand them anymore. After more than two decades as a professional programmer, I've stopped trying to make sense of certain things. I simply accept that sometimes, things don't work as they should and then I just move on and accept that I can't control certain conditions. So, don't try to figure out what happened. Just update the files with the new code I'm about to provide below. Don't ask me what happened, or how the code works. If I tried to explain, you'd probably think I've lost my mind. That being said, the new code to be used is provided in full below. There will be no attachments. Simply start using this new 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. };
Source code of C_Terminal.mqh
The code shown above should replace the old C_Terminal.mqh file. The code below should replace the old C_Mouse.mqh file.
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. //+------------------------------------------------------------------+
Source code of the C_Mouse.mqh file
Having made these changes, let's see what we need to do next. Because, as strange as it may sound, unexpected things sometimes happen. And you, as an aspiring programmer, need to understand that sometimes, you simply won't be able to figure out what's going on. This is because strange things do indeed happen from time to time. That's why I'm showing you each of the changes that are being made, so that you can see for yourself that not everything is within your control.
A New Mouse Indicator Emerges
At this stage, we need to make a rather unusual change for many developers. We need to enable the mouse indicator to handle all the events that would exist in a real market, but within the context of the replay/simulation. You might be thinking: "But isn't it already doing that?" No. Unfortunately, until now, I had isolated the replay/simulation system from the system that connects to the real server, whether on a demo account or a live account. Therefore, there were two modes of operation for the mouse indicator: one where it used book events, and another where these events were ignored.
The big issue here revolves around the order book events. However, these events are a bit more complex than you might think. It's not just a matter of calling the CustomBookAdd function, which is part of the MQL5 standard library, and expecting everything to work. The situation is a bit more complicated for us here, working in the replay/simulation environment. But there's no need to worry. We'll definitely reach the point where I'll explain how to use the CustomBookAdd function. Let's move without haste. Let's take it one step at a time.
As you may have noticed in the previous section, both the source code of the C_Terminal.mqh header file and the C_Mouse.mqh file were modified. Even though I didn't go into the details of these changes, you should know that changes were made. However, since they are relatively straightforward to understand, I don't see the need to go into detail about them. Just as there were changes made in those files, there are also changes in the C_Study.mqh header file. The complete code for this file can be found below.
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. //+------------------------------------------------------------------+
C_Study.mqh file source code
Unlike the other header files discussed in the previous section, the code that will be implemented here carries more weight, and the situation is a bit different.
Note that the constructor of the C_Study class has undergone a code clean-up process. In line 99, we changed the test value to prevent errors not originating from the code from prematurely terminating the execution. However, there's another point worth mentioning. Notice that we are no longer initializing Rate.close in the constructor. This initialization is now performed at a different point in the code. Look at line 114, where we check if the value is zero. If that's the case, in line 115, we capture the closing price, just as we did before.
This change was made because when the indicator was placed in a template and then added to the chart, there were times when Rate.close would get an incorrect value if we initialized it in the constructor. Honestly, I couldn't fully understand why this started happening. But the fact is that after the changes made to the code, we began experiencing random erratic initialization of Rate.close. To reduce the likelihood and impact of this issue, I moved the initialization point.
Now, there's an important detail: the check in line 114 will always return true when we execute line 100, because line 100 resets all values in the m_Info structure to zero. However, even though the entire code is presented in full, these were the points I felt deserved some explanation. The rest of the code remained virtually unchanged. Alright. Now let's move on to the changes made to the mouse indicator code. The complete code is shown below.
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. //+------------------------------------------------------------------+
Mouse Pointer source code
As you can see, the mouse indicator code is now very different. Actually, it's completely different from what it used to be. These changes were made for a reason. Even if you can't fully understand them, the idea is to make the mouse indicator more generic, while still maintaining consistency across any type of chart or instrument. Let's go through the changes.
First, we have the OnInit initialization function. If you look closely, you'll notice it's different from the previous version. Here, we need to pay attention to a few details. The first is that, in line 31, we check whether an error variable was set by any call we made. Previously, any error would cause initialization to report INIT_FAILED. So why did I change this? The reason is that sometimes the indicator, during initialization, reported an error that made no sense. But since the class constructor doesn't allow us to return a value, I was using the _LastError variable for this purpose. However, this proved to be not very practical. So, I decided to keep the same approach but filter the errors to something more specific using SetUserError. This type of error do not occur frequently, but it's important for us to confirm whether the constructors did their job correctly.
Once this is done, we introduce something that wasn't there before: we tell MetaTrader 5 that we want to receive order book events. Important: in the past, book events were only available when the chart data belonged to a real instrument, i.e., when connected to the trading server. Now, we always and consistently receive book events, even if it's a custom instrument. This is important. I intend to use the same mechanism to inform the user when the instrument is in auction mode. This makes the work much simpler since the interface stays the same, with no need for special adaptations. All we need to do is pass the correct data to the indicator, and it handles the rest.
Notice that in line 34, we start with the value indicating that the market is closed. Then, in lines 35 and 36, we initialize the mouse indicator's data buffer. It's a simple process that has been done before. The difference is that now we're explicitly telling MetaTrader 5 to always listen for order book events, regardless of the instrument.
Let's skip OnCalculate for now and return to it later. You may have noticed it no longer calls iSpread. Instead, let’s move forward to line 75, where we find the OnDeInit function. Because we'll be receiving book events for any instrument, we need to tell MetaTrader 5 that, at some point, we no longer want to receive them. This is done in line 77. And since we're removing the indicator from the chart, in line 79, we use the delete operator to release the memory back to the system.
Now, let's examine the order book event handler. It's at line 62, in the OnBookEvent routine. This is designed to be as simple as possible. We're not interested in analyzing the volume or the number of orders in the order book. What we care about is knowing if the instrument is in trading mode or in auction mode, and also whether the market is closed. To determine this, we just need to observe what's happening in the order book.
In line 67, we filter calls based on the instrument the indicator is attached to. This is because MetaTrader 5 does not filter order book events by instrument; it simply fires them to every chart that requested them. Therefore, it's not a good idea to watch multiple books for instruments we don't care about. This would generate unnecessary events and waste time. Once filtered, line 68 captures the latest order book data via an MQL5 library call. This is necessary so that this procedure could do its job.
Here comes the most interesting and useful part of OnBookEvent. Pay close attention to line 69. If the order book data array is empty, it means the market is closed. If there's data, we need a new check. Why? Simple: to know the origin of the data we're analyzing. You can see this in the C_Study.mqh header file, shown a bit earlier. In particular its Update procedure. In line 124, we call TimeCurrent if the instrument is connected to the trading server, or we use a global variable if it's a custom instrument in replay/simulation mode. That's why the test in line 69 is necessary. Without it, we'd risk using incorrect data, which would completely disrupt everything. Once we determine the symbol's primary status, we need to check its actual state. This is done in line 70, where we loop through the book looking for a specific type of information. In this case, we check the presence of BOOK_TYPE_BUY_MARKET or BOOK_TYPE_SELL_MARKET. If we find either, it means the market is open, but the instrument is in auction mode.
Notice how a simple step in OnInit, enabling order book events for any instrument, makes it possible to determine the instrument's current status. This kind of implementation detail is what makes this work so much fun.
Now let's turn our attention back to line 41, the OnCalculate procedure. You're probably wondering: Why was this code modified again? Did MetaTrader 5 receive an update between the last article and this one? Well, actually, no. Keep in mind, dear reader, that these articles were written a long time ago. But the content is only now being published.
The result of this execution can be seen in the video below, where I demonstrate what's actually happening. Before reading the explanation, watch the video. It will definitely help you understand what follows.
Demo video
If you watched the video, you probably noticed that I'm doing things in a very specific way. Don't worry for now about the code for the service, that will be covered at another time. For now, let's keep our focus here on the mouse indicator.
First, I initialized the service. It stayed in a waiting state to detect when the mouse indicator would be added to the chart. As soon as it was placed, you could observe that the display showed a countdown, with the values also appearing in the MetaTrader 5 toolbox. This was so we could verify whether the mouse indicator was actually able to access the values provided by the service.
Now, take note of the following point: In the previous article, at one point, the service continued sending data to the mouse indicator. However, the indicator was unable to provide us with a time-based update based on the values it was receiving from the service. This was because the display simply froze. Now observe that it froze when we were using the same code we're using at this moment in the OnCalculate function. To prevent the freeze back then, we resorted to using the iSpread call. However, here, we're not using that call, and yet the indicator display didn't freeze. Why did it work now, but not in the previous article? Honestly, I don't know the answer to this question. It's one of those things that only those who truly experiment and push the limits will ever get to experience firsthand.
I'm sharing this with you, dear reader, to show you that as programmers, we don't always have the answer to everything. There are things that we encounter, but we don't know why or how we arrived at that result. In my view, sharing this kind of situation with you makes both the learning process and my own experience much more enjoyable. Because you'll learn that not all objectives are achieved from the started. And for me, I learn how each of the tiniest parts of MetaTrader 5 works and how far I can push it without causing a complete system crash. So, to wrap up this article, let's take a look at the source code for the service used in the video.
Testing Service
Typically, people struggle to learn programming because they don't understand that:
THE ORDER OF EVENTS AFFECTS THE OUTCOME
But when it comes to programming, this is true in most cases. Rarely can we program something without worrying about the order in which events occur. But here, in the testing service, the above statement is absolutely true. So let's take a look at the source code of the service.
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. //+------------------------------------------------------------------+
Source code of the test service
Notice that the code is practically the same as what was seen in the previous article, intended to test the mouse indicator. However, there are a few new elements here. These are the elements that triggered the entire process you saw in the video. Every new line here has a reason and meaning. Most people attempting to use the book on a custom symbol fail because they don't understand what really needs to be done. This is because they focus solely on the CustomBookAdd call, which can be seen on lines 47, 62, and 64. But that's not how things work. Simply trying to pass data to the book on a custom symbol using only the CustomBookAdd library call will absolutely not work, unless we take a few prior steps. And, to avoid mistakes, these steps must be carefully planned.
The main step to take is in line 24. Without it, the CustomBookAdd function is completely useless for us. But isn't CustomBookAdd specifically for sending data to the order book? Just like the functions for Tick and Rate? The answer to all these questions is both yes and no. It sounds contradictory, but the CustomBookAdd function will only have any effect if you first define a value for SYMBOL_TICKS_BOOKDEPTH. The value to use for SYMBOL_TICKS_BOOKDEPTH depends on what we intend to do. Here, since our goal is just to guide the mouse indicator so that it can inform the user about the status of the custom symbol, we only need a single level. For this reason, the value I used in the call on line 24 to define SYMBOL_TICKS_BOOKDEPTH is 1. However, if you want to create an artificial book for simulation or replay studies, you just need to change the value from one to whatever suits your needs.
In any case, this is the basic part. Only after defining SYMBOL_TICKS_BOOKDEPTH can we actually receive data in the MqlBookInfo structure. This aspect was somewhat tricky for me to understand, as I couldn't find any reference explaining how to populate the MqlBookInfo structure for a custom symbol. All references simply said to use CustomBookAdd. However, only the documentation mentioned SYMBOL_TICKS_BOOKDEPTH. It did not make the connection between this definition and the ability to access the data in the MqlBookInfo structure posted by CustomBookAdd.
In any case, this service will be used solely for testing data transmission. It won't serve any other purpose. But before wrapping up this article, I'd like to quickly explain what's happening here. Between lines 44 and 46, we define a book level. The most important part for us here is line 44. On line 47, we inform MetaTrader 5 to generate an event and make the data available as the book. Now, pay close attention to this: on line 50, we capture the time. On line 56, we increment the time by one second. As defined on line 34, the initial value of the time variable will be 32400. This is in seconds. Don't forget this point. So, on line 58, we perform a check to determine our current position. After 30 seconds we send a new value to the book on line 62. The value to be sent is shown on line 61. This value will cause the mouse indicator to show that the symbol is in auction mode. After another 30 seconds, the check will cause line 64 to execute, meaning the value to be sent to the book will be from line 65 via the execution of line 66. This will make the time display return on the mouse indicator.
One last detail: the time doesn't correspond exactly to one second, since on line 55 we're introducing a delay of a quarter of a second.
Final Thoughts
In this article, I showed you how to pass values to the order book on a custom symbol. Although the only purpose of the service we use is to test this way of passing values, we have demonstrated that we can do it in a completely reasonable way. Despite the initial challenges, it is indeed possible. Therefore, you now have another way to transfer information between a service and an indicator or Expert Advisor using the order book. However, you should always remember that everything comes at a cost. And using the order book indiscriminately will come with its own price that must be considered.
In any case, this test service doesn't reflect what we'll be doing in the next article. We will apply a replay/simulator to inform us whether the symbol is in auction mode or not. Isn't this system interesting? So, I'll see you in the next article.
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/12326





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use