Market Replay: Unity Is Strength (I)
Introduction
Hello, everyone, and welcome to a new article in our series on building a replay/simulation system.
In my previous article “Market Replay: Position View (XX)”, I showed how to modify the position indicator to create a shadow at the level where the price had been. However, this implementation has a minor drawback. The problem arises when you drag a take profit or stop loss and decide to change the way the trade result is displayed—for example, from a monetary value to a percentage. Although some traders might want to do this while dragging it, that option is not available. First, you need to finish dragging it, and only then should you change the data display mode. After that, you'll need to start moving it again for the information to display correctly.
Although it would be fairly easy to solve this problem, I personally don’t think it’s necessary either to solve it or to show how it could be solved. I think those who have been following this series are probably eager to see the finished system as soon as possible. For this reason, in this article we decided to move directly to the stage where we integrate the four components into the replay/simulation system, since they have already been sufficiently developed for use in this system.
It is clear that the pending-orders system has yet to be developed. However, since this system makes extensive use of the position indicator and requires only a few changes to it, we will create the pending-orders system directly within the replay/simulation system.
Once this decision has been made, in this article we will make the necessary changes to begin using the position indicator in the replay/simulation system, at least in its most basic form. So, let's get down to business and start making some changes.
Let's take a look at what we have right now
First, I'd like you to take a look at what the code for the C_ElementsTrade class looks like now, after removing the duplicate code that was in the previous article. You can see the complete code below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 005. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 006. #define def_NameBtnMove m_Info.szPrefixName + "#MOVE" 007. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT" 008. #define def_NameObjLabel m_Info.szPrefixName + "#PROFIT" 009. #define def_NameBackGround m_Info.szPrefixName + "#BACKGROUND" 010. #define def_NameVolume m_Info.szPrefixName + "#VOLUME" 011. //+------------------------------------------------------------------+ 012. #define def_GhostColor clrDarkGray 013. #define macro_GhostName(A) (m_Info.szPrefixName + "[GHOST]" + (A != "" ? StringSubstr(A, StringFind(A, "#")) : "")) 014. //+------------------------------------------------------------------+ 015. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 016. //+------------------------------------------------------------------+ 017. #define def_PathBtns "Images\\Market Replay\\Orders\\" 018. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 019. #resource "\\" + def_Btn_Close; 020. //+------------------------------------------------------------------+ 021. #include "..\Auxiliar\C_Mouse.mqh" 022. //+------------------------------------------------------------------+ 023. #ifdef def_FontName 024. "Why are you trying to do this?" 025. #else 026. #define def_FontName "Lucida Console" 027. #define def_FontSize 10 028. #endif 029. //+------------------------------------------------------------------+ 030. class C_ElementsTrade : private C_Mouse 031. { 032. private : 033. //+------------------------------------------------------------------+ 034. struct stInfos 035. { 036. struct st_01 037. { 038. short Width, 039. Height, 040. digits; 041. }Text; 042. ulong ticket; 043. string szPrefixName, 044. szDescr, 045. szSymbol, 046. szMsgGhost; 047. EnumEvents ev; 048. double price, 049. open, 050. volume, 051. var, 052. tickSize, 053. tpsl, 054. limit, 055. priceGhost; 056. bool bClick, 057. bIsBuy; 058. char weight; 059. color _color; 060. int sizeText; 061. enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode; 062. }m_Info; 063. //+------------------------------------------------------------------+ 064. short UpdateViewPort(const double price, short size = 0, uint ui = 0) 065. { 066. #define macro_Local(A) { \ 067. ChartTimePriceToXY(0, 0, 0, (A ? price : m_Info.priceGhost), x, y); \ 068. x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2))); \ 069. ObjectSetInteger(0, (A ? def_NameHLine : macro_GhostName(def_NameHLine)), OBJPROP_XDISTANCE, x); \ 070. ObjectSetInteger(0, (A ? def_NameHLine : macro_GhostName(def_NameHLine)), OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); \ 071. ObjectSetInteger(0, (A ? def_NameObjLabel : macro_GhostName(def_NameObjLabel)), OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0)); \ 072. ObjectSetInteger(0, (A ? def_NameObjLabel : macro_GhostName(def_NameObjLabel)), OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); \ 073. ObjectSetInteger(0, (A ? def_NameBtnMove : macro_GhostName(def_NameBtnMove)), OBJPROP_XDISTANCE, x + _Width + 20); \ 074. ObjectSetInteger(0, (A ? def_NameBtnMove : macro_GhostName(def_NameBtnMove)), OBJPROP_YDISTANCE, y); \ 075. ObjectSetInteger(0, (A ? def_NameBackGround : macro_GhostName(def_NameBackGround)), OBJPROP_XDISTANCE, x - 10); \ 076. ObjectSetInteger(0, (A ? def_NameBackGround : macro_GhostName(def_NameBackGround)), OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2)); } 077. 078. static short _SizeControls; 079. static short _Width; 080. uint x, y; 081. 082. if (size > 0) 083. { 084. size += (short)(ui + 8); 085. _SizeControls = (_SizeControls > size ? _SizeControls : size); 086. size = (short)(_SizeControls - ui - 12); 087. _Width = (_Width > size ? _Width : size); 088. }else 089. { 090. macro_Local(true); 091. ObjectSetInteger(0, def_NameVolume, OBJPROP_XDISTANCE, x + 10); 092. ObjectSetInteger(0, def_NameVolume, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 093. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 094. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 095. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + (_Width * 2) + 20); 096. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 097. if (m_Info.szMsgGhost != "") 098. macro_Local(false) 099. } 100. return _Width; 101. #undef macro_Local 102. } 103. //+------------------------------------------------------------------+ 104. inline void CreateLinePrice(void) 105. { 106. #define macro_Local(A) \ 107. CreateObjectGraphics(szObj = A, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); \ 108. ObjectSetInteger(0, A, OBJPROP_BGCOLOR, m_Info._color); \ 109. ObjectSetInteger(0, A, OBJPROP_BORDER_TYPE, BORDER_FLAT); \ 110. ObjectSetInteger(0, A, OBJPROP_CORNER, CORNER_LEFT_UPPER); 111. 112. string szObj; 113. 114. macro_Local(def_NameHLine); 115. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 116. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 117. macro_LineInFocus(false); 118. macro_Local(def_NameBackGround); 119. #undef macro_Local 120. } 121. //+------------------------------------------------------------------+ 122. inline void CreateBoxInfo(const bool bMove) 123. { 124. string szObj; 125. const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0}; 126. 127. CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 128. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 129. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 130. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon))); 131. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15)); 132. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 133. } 134. //+------------------------------------------------------------------+ 135. inline void CreateObjectInfoText(const string szObj, const color _color) 136. { 137. CreateObjectGraphics(szObj, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault)); 138. ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName); 139. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize); 140. ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack); 141. ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, _color); 142. ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER); 143. ObjectSetInteger(0, szObj, OBJPROP_READONLY, true); 144. } 145. //+------------------------------------------------------------------+ 146. inline void CreateButtonClose(void) 147. { 148. string szObj; 149. 150. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 151. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 152. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 153. } 154. //+------------------------------------------------------------------+ 155. inline void AdjustDinamic(const string szObj, const string szTxt) 156. { 157. #define macro_Local(A) \ 158. ObjectSetInteger(0, A, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0)); \ 159. ObjectSetInteger(0, A, OBJPROP_YSIZE, m_Info.Text.Height + 5); 160. 161. uint w, h; 162. 163. TextSetFont(def_FontName, def_FontSize * -10); 164. TextGetSize(szTxt, w, h); 165. m_Info.Text.Height = (uchar) h + 4; 166. m_Info.Text.Width = (uchar) w + 4; 167. m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32); 168. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width); 169. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height); 170. macro_Local(def_NameBackGround); 171. if (m_Info.szMsgGhost != "") 172. { 173. macro_Local(macro_GhostName(def_NameBackGround)); 174. ObjectSetString(0, macro_GhostName(def_NameObjLabel), OBJPROP_TEXT, m_Info.szMsgGhost); 175. } 176. #undef macro_Local 177. } 178. //+------------------------------------------------------------------+ 179. inline void ChartChange(void) 180. { 181. UpdateViewPort(MathAbs(m_Info.price)); 182. m_Info.limit = (m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 183. if (m_Info.ev != evMsgClosePositionEA) 184. ViewValue(m_Info.limit); 185. } 186. //+------------------------------------------------------------------+ 187. void CreateGhost(void) 188. { 189. ObjectSetInteger(0, def_NameHLine, OBJPROP_BGCOLOR, def_GhostColor); 190. ObjectSetInteger(0, def_NameHLine, OBJPROP_COLOR, def_GhostColor); 191. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, def_GhostColor); 192. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BORDER_COLOR, def_GhostColor); 193. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_COLOR, def_GhostColor); 194. ObjectSetInteger(0, def_NameBackGround, OBJPROP_BGCOLOR, def_GhostColor); 195. ObjectSetInteger(0, def_NameBackGround, OBJPROP_COLOR, def_GhostColor); 196. ObjectSetString(0, def_NameHLine, OBJPROP_NAME, macro_GhostName(def_NameHLine)); 197. ObjectSetString(0, def_NameBtnMove, OBJPROP_NAME, macro_GhostName(def_NameBtnMove)); 198. ObjectSetString(0, def_NameObjLabel, OBJPROP_NAME, macro_GhostName(def_NameObjLabel)); 199. ObjectSetString(0, def_NameBackGround, OBJPROP_NAME, macro_GhostName(def_NameBackGround)); 200. m_Info.priceGhost = MathAbs(m_Info.price); 201. m_Info.szMsgGhost = ""; 202. } 203. //+------------------------------------------------------------------+ 204. public : 205. //+------------------------------------------------------------------+ 206. C_ElementsTrade(const ulong ticket, string szSymbol, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true) 207. :C_Mouse(0, "") 208. { 209. ZeroMemory(m_Info); 210. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 211. m_Info._color = _color; 212. m_Info.szDescr = szDescr; 213. m_Info.bIsBuy = IsBuy; 214. m_Info.Text.digits = digits; 215. m_Info.tickSize = ticksize; 216. m_Info.szSymbol = szSymbol; 217. } 218. //+------------------------------------------------------------------+ 219. ~C_ElementsTrade() 220. { 221. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 222. ObjectsDeleteAll(0, m_Info.szPrefixName); 223. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 224. } 225. //+------------------------------------------------------------------+ 226. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0, const double special = -1) 227. { 228. m_Info.sizeText = 0; 229. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 230. ObjectsDeleteAll(0, m_Info.szPrefixName + "#"); 231. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 232. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 233. m_Info.var = (var > 0 ? var : m_Info.var); 234. m_Info.tpsl = (special >= 0 ? special : m_Info.tpsl); 235. if (price > 0) 236. { 237. CreateLinePrice(); 238. CreateButtonClose(); 239. CreateObjectInfoText(def_NameObjLabel, m_Info._color); 240. } 241. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 242. m_Info.open = open; 243. m_Info.price = (price > 0 ? price : -open); 244. if (m_Info.ev == evMsgClosePositionEA) 245. { 246. CreateObjectInfoText(def_NameVolume, clrBlack); 247. ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet); 248. ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0))); 249. AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : "")); 250. }; 251. ChartChange(); 252. } 253. //+------------------------------------------------------------------+ 254. void ViewValue(const double profit, const bool Enabled = true) 255. { 256. string szTxt; 257. color _cor; 258. static double memSL, memTP; 259. 260. if (Enabled) 261. { 262. switch (m_Info.ViewMode) 263. { 264. case stInfos::eValue: 265. szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit)); 266. break; 267. case stInfos::eFinance: 268. szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume); 269. break; 270. case stInfos::eTicks: 271. szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize)); 272. break; 273. case stInfos::ePercentage: 274. szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2)); 275. break; 276. } 277. m_Info.szMsgGhost = ((m_Info.szMsgGhost == "") && (m_Info.ev != evMsgClosePositionEA) ? szTxt : m_Info.szMsgGhost); 278. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt); 279. if (StringLen(szTxt) != m_Info.sizeText) 280. { 281. AdjustDinamic(def_NameObjLabel, szTxt); 282. m_Info.sizeText = StringLen(szTxt); 283. } 284. } 285. _cor = (m_Info.limit >= 0 ? clrPaleGreen : clrCoral); 286. switch (m_Info.ev) 287. { 288. case evMsgCloseTakeProfit: 289. memTP = (Enabled ? memTP : profit); 290. _cor = (m_Info.limit < memTP ? clrYellow : _cor); 291. break; 292. case evMsgCloseStopLoss: 293. memSL = (Enabled ? memSL : profit); 294. _cor = (m_Info.limit > memSL ? clrYellow : _cor); 295. break; 296. case evMsgClosePositionEA: 297. _cor = (profit >= 0 ? clrPaleGreen : clrCoral); 298. break; 299. } 300. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, _cor); 301. } 302. //+------------------------------------------------------------------+ 303. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 304. { 305. string sz0; 306. long _lparam = lparam; 307. double _dparam = dparam; 308. 309. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 310. switch (id) 311. { 312. case (CHARTEVENT_KEYDOWN): 313. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 314. _lparam = (long) m_Info.ticket; 315. _dparam = 0; 316. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 317. case CHARTEVENT_CUSTOM + evMsgSetFocus: 318. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 319. { 320. CreateGhost(); 321. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 322. }else 323. { 324. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 325. ObjectsDeleteAll(0, macro_GhostName("")); 326. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 327. } 328. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 329. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 330. m_Info.bClick = false; 331. case CHARTEVENT_CHART_CHANGE: 332. ChartChange(); 333. break; 334. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 335. m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03); 336. EventChartCustom(0, evUpdate_Position, m_Info.ticket, 0, NULL); 337. break; 338. case CHARTEVENT_OBJECT_CLICK: 339. sz0 = GetPositionsMouse().szObjNameClick; 340. if (m_Info.bClick) 341. { 342. if (sz0 == def_NameBtnMove) 343. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 344. if (sz0 == def_NameBtnClose) 345. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, m_Info.tpsl, m_Info.szSymbol); 346. if (sz0 == def_NameObjLabel) 347. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL); 348. } 349. m_Info.bClick = false; 350. break; 351. case CHARTEVENT_MOUSE_MOVE: 352. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 353. if (m_Info.weight > 1) 354. { 355. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 356. ViewValue(m_Info.limit = (m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam)); 357. if (m_Info.bClick) 358. { 359. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 360. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, m_Info.szSymbol); 361. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 362. } 363. } 364. break; 365. case CHARTEVENT_OBJECT_DELETE: 366. if (StringFind(sparam, m_Info.szPrefixName) < 0) break; 367. UpdatePrice(m_Info.open, m_Info.price); 368. break; 369. } 370. } 371. //+------------------------------------------------------------------+ 372. }; 373. //+------------------------------------------------------------------+ 374. #undef def_GhostColor 375. #undef macro_GhostName 376. //+------------------------------------------------------------------+ 377. #undef macro_LineInFocus 378. //+------------------------------------------------------------------+ 379. #undef def_Btn_Close 380. #undef def_PathBtns 381. #undef def_FontName 382. #undef def_FontSize 383. //+------------------------------------------------------------------+ 384. #undef def_NameVolume 385. #undef def_NameBackGround 386. #undef def_NameObjLabel 387. #undef def_NameInfoDirect 388. #undef def_NameBtnMove 389. #undef def_NameBtnClose 390. #undef def_NameHLine 391. //+------------------------------------------------------------------+
C_ElementsTrade
I don't think you'll have any trouble understanding this code, since it's completely consistent with what we've covered so far. However, all duplicate code here has been removed and replaced with macros. Thus, although the code still appears to be duplicated from the compiler's perspective, for us—since we need to read and modify the code—this duplication effectively ceases to exist. If we change the macro code, we'll only need to make that change once.
To better understand how macros are used in this way, take a look at line 66, where the macro is defined. This macro has a local scope, so it is removed on line 101. I want you to note that we use the macro in two places: on lines 90 and 98. In these places, the compiler takes the macro code and inserts it directly. So, from the compiler's perspective, the code is indeed duplicated, but not from our perspective. If you want to change the starting position along the X-axis, you only need to make this change once—specifically on line 68. This change will affect both the shadow and the displayed segment. It's that simple.
Please also note that, since we have eliminated any dependence on the symbol and any associated information, we no longer need to worry about the C_ElementsTrade class when porting the indicator to the replay/simulation system. From this point on, we will focus entirely on the main code for the indicator, which is provided in full below. Keep in mind that we're talking about the previous version—the very one we discussed before the last article.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Positions.ico" 004. #property description "Indicator for tracking an open position on the server." 005. #property description "This should preferably be used together with an Expert Advisor." 006. #property description "For more details see the same article." 007. #property version "1.132" 008. #property link "https://www.mql5.com/pt/articles/13523" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. //+------------------------------------------------------------------+ 012. #define def_ShortName "Position View" 013. //+------------------------------------------------------------------+ 014. #include <Market Replay\Order System\C_ElementsTrade.mqh> 015. #include <Market Replay\Defines.mqh> 016. //+------------------------------------------------------------------+ 017. input ulong user00 = 0; //For use in an Expert Advisor 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szShortName, 023. szSymbol; 024. double priceOpen, 025. var, 026. tickSize; 027. char digits; 028. bool bIsBuy; 029. }m_Infos; 030. //+------------------------------------------------------------------+ 031. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 032. //+------------------------------------------------------------------+ 033. bool CheckCatch(ulong ticket) 034. { 035. double vv; 036. 037. ZeroMemory(m_Infos); 038. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 039. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 040. if (ChartWindowFind(0, m_Infos.szShortName) >= 0) 041. { 042. m_Infos.ticket = 0; 043. return false; 044. } 045. m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL); 046. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 047. m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 048. vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 049. m_Infos.var = m_Infos.tickSize / vv; 050. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 051. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 052. 053. return true; 054. } 055. //+------------------------------------------------------------------+ 056. inline void ProfitNow(void) 057. { 058. double ask, bid, value; 059. 060. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 061. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 062. if (Open != NULL) 063. { 064. (*Open).ViewValue(value = (m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 065. (*Take).ViewValue(value, false); 066. (*Stop).ViewValue(value, false); 067. } 068. } 069. //+------------------------------------------------------------------+ 070. int OnInit() 071. { 072. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 073. if (!CheckCatch(user00)) 074. { 075. ChartIndicatorDelete(0, 0, def_ShortName); 076. return INIT_FAILED; 077. } 078. 079. return INIT_SUCCEEDED; 080. } 081. //+------------------------------------------------------------------+ 082. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 083. { 084. ProfitNow(); 085. 086. return rates_total; 087. } 088. //+------------------------------------------------------------------+ 089. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 090. { 091. double volume; 092. 093. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 094. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 095. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 096. switch (id) 097. { 098. case CHARTEVENT_CUSTOM + evUpdate_Position: 099. if (lparam != m_Infos.ticket) break; 100. if (!PositionSelectByTicket(m_Infos.ticket)) 101. { 102. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 103. return; 104. }; 105. if (Open == NULL) Open = new C_ElementsTrade( 106. m_Infos.ticket, 107. m_Infos.szSymbol, 108. evMsgClosePositionEA, 109. clrRoyalBlue, 110. m_Infos.digits, 111. m_Infos.tickSize, 112. StringFormat("%I64u : Position opening price.", m_Infos.ticket), 113. m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) 114. ); 115. if (Take == NULL) Take = new C_ElementsTrade( 116. m_Infos.ticket, 117. m_Infos.szSymbol, 118. evMsgCloseTakeProfit, 119. clrForestGreen, 120. m_Infos.digits, 121. m_Infos.tickSize, 122. StringFormat("%I64u : Take Profit price.", m_Infos.ticket), 123. m_Infos.bIsBuy 124. ); 125. if (Stop == NULL) Stop = new C_ElementsTrade( 126. m_Infos.ticket, 127. m_Infos.szSymbol, 128. evMsgCloseStopLoss, 129. clrFireBrick, 130. m_Infos.digits, 131. m_Infos.tickSize, 132. StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), 133. m_Infos.bIsBuy 134. ); 135. volume = PositionGetDouble(POSITION_VOLUME); 136. (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 137. (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var, PositionGetDouble(POSITION_SL)); 138. (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var, PositionGetDouble(POSITION_TP)); 139. ProfitNow(); 140. break; 141. } 142. ChartRedraw(); 143. }; 144. //+------------------------------------------------------------------+ 145. void OnDeinit(const int reason) 146. { 147. delete Open; 148. delete Take; 149. delete Stop; 150. } 151. //+------------------------------------------------------------------+
Position indicator
As you can see, the entire set of dependencies is here. This code was explained in detail in previous articles until it reached its current state. From this point on, all changes will be made to the code shown above. The goal is to isolate—or, more precisely, to separate—the replay/simulation system from the real market. However, even if we make this separation, the position indicator will remain ready to work in both situations. The indicator itself will not be able to determine whether it is running in the replay/simulation system or is connected to a real trading server—whether via a demo account or a live account.
First of all, we need to obtain the name of the symbol that we will use in the replay/simulation system. This is how we will begin separating these two things. This name is declared in the Defines.mqh header file. Please refer to the previous articles for more details. We only need to use the definition from this file, which is already included on line 15 of the main code.
At first glance, this task seems relatively simple, doesn't it? However, it turns out to be much more time-consuming than it might seem. The reason is that all calls to PositionGetDouble, PositionGetInteger, and PositionSelectByTicket are blocking points to using the replay/simulation system. These are precisely the obstacles we need to work around so that the position indicator can actually be used in the replay/simulation system.
Therefore, we need to find a solution that will allow us to start working effectively. At this stage, many people get stuck, unable to move on to the next implementation step because it seems impossible. But is that really the case? Let me remind you of something I explained some time ago in this same series of articles: the use of SQL and the interaction between MetaTrader 5 and Excel. When I brought up these topics, you may have thought they were of no use in a series focused on building a replay/simulation system. Why explain this in this context? Well, if you haven't studied these materials yet, I recommend finding them in this series and reviewing them, because it's time to put this knowledge into practice.
Understanding the Choice of Solution
For practical reasons rather than because of programming complexity—we will use an SQL-based solution for the replay/simulation system. The fact is that MQL5 already provides tools for working with SQL without the need for external programming. Keep in mind that my goal is to show that we can build a replay/simulation system entirely in MQL5. However, if you need to or prefer to, you can set things up to use Excel alongside MetaTrader 5 and thus obtain metrics for your trades and research within the replay/simulation system. However, I won't explain how to do that here. We will use an SQL database directly.
This might raise the question: Why not use a separate file—text or binary—to simulate a server? Wouldn't that be easier than using an SQL database? That's a valid question, and, in principle, yes, it would be easier. However, keep the following in mind: if the operations performed in the replay/simulation system were saved to a file created solely for that purpose, we would subsequently have to do additional work to analyze them. If you store everything in an SQL database, there's no need for this extra work. It is much easier to read the contents of an SQL database than to parse the contents of a file specifically created to store operations performed in a replay/simulation system.
Thus, the issue is not its use within the replay/simulation system, but rather how easy it is to later read what was done in the replay/simulation system. That is exactly why we will be using an SQL database.
From this point on, things will take a slightly different turn. The position indicator should not create a database file; it should only read from that file. The task of creating the file will be assigned to the Expert Advisor, which will effectively simulate the trading server. The position indicator will only display data contained in the database created by the Expert Advisor.
All right. This is the starting point. However, before we begin, there are a few things we need to consider. Since the last update to the replay/simulation service, the helper classes have undergone significant changes. This is one of the challenges that make it difficult to develop larger projects. Many novice programmers end up simply giving up on such projects. Others, although they do not give up, get caught in a vicious cycle that prevents them from truly returning to their original project.
If you try to use the replay/simulation service now, after all this time, you'll notice that the executable file doesn't work as it should. And if you recompile the service based on the old code, you will see that it is impossible to start the replay process. Even if you manage to force the replay to start, you won't be able to interact with the control indicator. This is because the replay/simulation system's code contains several outdated elements. Therefore, before we continue, we need to update the old replay/simulation system code. There's no need to worry: the update will be carried out in a simple and straightforward manner.
Replay/Simulation System Update
The first update should be made in the C_DrawImage class, as shown in the following code snippet.
138. //+------------------------------------------------------------------+ 139. C_DrawImage(C_Terminal *ptr, string szObjName, const color cFilter, const string szFile1, const string szFile2, const bool r180 = false) 140. { 141. (*ptr).CreateObjectGraphics(m_szObjName = szObjName, OBJ_BITMAP_LABEL, ePriorityDefault); 142. ObjectSetString(m_ID = (*ptr).GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName = "::" + m_szObjName); 143. Initilize(0, cFilter, szFile1, r180); 144. if (szFile2 != NULL) Initilize(1, cFilter, szFile2, r180); 145. } 146. //+------------------------------------------------------------------+
Snippet from C_DrawImage
The change is on line 141 and sets the value of the OBJPROP_ZORDER property. Without this setting, objects created by this class would have the same ZORDER value as objects of the C_Mouse class. Since C_DrawImage is used in the control indicator, the OBJPROP_ZORDER value must be increased so that the C_Controls class can correctly intercept clicks on objects.
The next update will require a little more work, since it affects the C_Controls class. Although many might think these changes aren't necessary, MetaTrader 5 has actually received numerous updates and improvements over this time as well. These updates and improvements have increased the program's stability and enhanced many other aspects as well. Therefore, we need to update our replay/simulation system. Nevertheless, I believe this will be the last time we need to update the replay/simulation system. Below you can see the new, updated code for the C_Controls class.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonCtrl def_PathBMP + "Ctrl.bmp" 011. #define def_ButtonCtrlBlock def_PathBMP + "Ctrl_Block.bmp" 012. #define def_ButtonPin def_PathBMP + "Pin.bmp" 013. #resource "\\" + def_ButtonPlay 014. #resource "\\" + def_ButtonPause 015. #resource "\\" + def_ButtonCtrl 016. #resource "\\" + def_ButtonCtrlBlock 017. #resource "\\" + def_ButtonPin 018. //+------------------------------------------------------------------+ 019. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A)) 020. #define def_PosXObjects 120 021. //+------------------------------------------------------------------+ 022. #define def_SizeButtons 32 023. #define def_ColorFilter 0xFF00FF 024. //+------------------------------------------------------------------+ 025. #include "..\Auxiliar\C_Mouse.mqh" 026. //+------------------------------------------------------------------+ 027. class C_Controls : private C_Mouse 028. { 029. protected: 030. private : 031. //+------------------------------------------------------------------+ 032. enum eMatrixControl {eCtrlPosition, eCtrlStatus}; 033. enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)}; 034. //+------------------------------------------------------------------+ 035. struct st_00 036. { 037. string szBarSlider, 038. szBarSliderBlock; 039. ushort Minimal; 040. }m_Slider; 041. struct st_01 042. { 043. C_DrawImage *Btn; 044. bool state; 045. short x, y, w, h; 046. }m_Section[eObjectControl::eNull]; 047. //+------------------------------------------------------------------+ 048. inline void CreteBarSlider(short x, short size) 049. { 050. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 051. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 052. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 053. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 054. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 055. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 059. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 064. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 065. } 066. //+------------------------------------------------------------------+ 067. void SetPlay(bool state) 068. { 069. if (m_Section[ePlay].Btn == NULL) 070. m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay); 071. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start"); 072. if (!state) CreateCtrlSlider(); 073. } 074. //+------------------------------------------------------------------+ 075. void CreateCtrlSlider(void) 076. { 077. if (m_Section[ePin].Btn != NULL) return; 078. CreteBarSlider(77, 436); 079. m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock); 080. m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true); 081. m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL); 082. PositionPinSlider(m_Slider.Minimal); 083. } 084. //+------------------------------------------------------------------+ 085. inline void RemoveCtrlSlider(void) 086. { 087. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 088. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 089. { 090. delete m_Section[c0].Btn; 091. m_Section[c0].Btn = NULL; 092. } 093. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B")); 094. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 095. } 096. //+------------------------------------------------------------------+ 097. inline void PositionPinSlider(ushort p) 098. { 099. int iL, iR; 100. string szMsg; 101. 102. m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 103. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 104. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 105. m_Section[ePin].x += def_PosXObjects; 106. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 107. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) if (m_Section[c0].Btn != NULL) 108. { 109. switch (c0) 110. { 111. case eLeft : szMsg = "Previous Position"; break; 112. case eRight : szMsg = "Next Position"; break; 113. case ePin : szMsg = "Go To: " + IntegerToString(p); break; 114. default szMsg = "\n"; 115. } 116. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg); 117. } 118. 119. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 120. } 121. //+------------------------------------------------------------------+ 122. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 123. { 124. x = (short) GetPositionsMouse().Position.X_Graphics; 125. y = (short) GetPositionsMouse().Position.Y_Graphics; 126. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 127. { 128. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && 129. ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 130. return c0; 131. } 132. 133. return eNull; 134. } 135. //+------------------------------------------------------------------+ 136. public : 137. //+------------------------------------------------------------------+ 138. C_Controls(const long Arg0, const string szShortName) 139. :C_Mouse(Arg0, "") 140. { 141. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 142. if (_LastError >= ERR_USER_ERROR_FIRST) return; 143. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 144. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 145. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 146. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 147. { 148. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 149. m_Section[c0].y = 25; 150. m_Section[c0].Btn = NULL; 151. } 152. m_Section[ePlay].x = def_PosXObjects; 153. m_Section[eLeft].x = m_Section[ePlay].x + 47; 154. m_Section[eRight].x = m_Section[ePlay].x + 511; 155. m_Slider.Minimal = eTriState; 156. } 157. //+------------------------------------------------------------------+ 158. ~C_Controls() 159. { 160. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 161. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 162. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 163. } 164. //+------------------------------------------------------------------+ 165. void SetBuffer(const int rates_total, double &Buff[]) 166. { 167. uCast_Double info; 168. 169. info._16b[eCtrlPosition] = m_Slider.Minimal; 170. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause)); 171. info._8b[def_IndexTimeFrame] = (uchar) def_IndicatorTimeFrame; 172. if (rates_total > 0) 173. Buff[rates_total - 1] = info.dValue; 174. } 175. //+------------------------------------------------------------------+ 176. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 177. { 178. short x, y; 179. static ushort iPinPosX = 0; 180. static short six = -1, sps; 181. uCast_Double info; 182. 183. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 184. switch (id) 185. { 186. case (CHARTEVENT_CUSTOM + evMsgServiceSwapStatus): 187. SetPlay(!m_Section[ePlay].state); 188. if (m_Section[ePlay].state) 189. { 190. RemoveCtrlSlider(); 191. m_Slider.Minimal = iPinPosX; 192. }else CreateCtrlSlider(); 193. break; 194. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 195. info.dValue = dparam; 196. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 197. iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : 198. (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition])); 199. SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay); 200. break; 201. case CHARTEVENT_OBJECT_DELETE: 202. if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName("")) 203. { 204. if (sparam == def_ObjectCtrlName(ePlay)) 205. { 206. delete m_Section[ePlay].Btn; 207. m_Section[ePlay].Btn = NULL; 208. SetPlay(m_Section[ePlay].state); 209. }else 210. { 211. RemoveCtrlSlider(); 212. CreateCtrlSlider(); 213. } 214. } 215. break; 216. case CHARTEVENT_MOUSE_MOVE: 217. if (CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 218. { 219. case ePlay: 220. EventChartCustom(GetInfoTerminal().ID, evMsgServiceSwapStatus, 0, 0, ""); 221. break; 222. case eLeft: 223. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 224. break; 225. case eRight: 226. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 227. break; 228. case ePin: 229. if (six == -1) 230. { 231. six = x; 232. sps = (short)iPinPosX; 233. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 234. } 235. iPinPosX = sps + x - six; 236. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 237. break; 238. }else if (six > 0) 239. { 240. six = -1; 241. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 242. } 243. break; 244. } 245. ChartRedraw(); 246. } 247. //+------------------------------------------------------------------+ 248. }; 249. //+------------------------------------------------------------------+ 250. #undef def_PosXObjects 251. #undef def_ButtonPlay 252. #undef def_ButtonPause 253. #undef def_ButtonCtrl 254. #undef def_ButtonCtrlBlock 255. #undef def_ButtonPin 256. #undef def_PathBMP 257. //+------------------------------------------------------------------+
C_Controls
I won't go into detail about the changes. This will remain an exercise for you: study the code and try to figure out exactly what was changed. However, I'll give you a hint: take a look at the constructor on line 138 and the class declaration on line 27. Both were modified, as were some other parts of the code. All of these changes are related to a change in inheritance. Since the constructor has changed, the main code has also changed. The section below shows only the part that actually needed to be updated.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.133" 07. #property link "https://www.mql5.com/pt/articles/13531" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. #property indicator_buffers 1 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Service Graphics\C_Controls.mqh> 13. //+------------------------------------------------------------------+ 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. double m_Buff[]; 19. int m_RatesTotal = 0; 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control")) == POINTER_INVALID) 24. SetUserError(C_Terminal::ERR_PointerInvalid); 25. if ((_LastError >= ERR_USER_ERROR_FIRST) || (user00 == 0)) 26. { 27. Print("Control indicator failed on initialization."); 28. return INIT_FAILED; 29. } 30. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 31. ArrayInitialize(m_Buff, EMPTY_VALUE); 32. 33. return INIT_SUCCEEDED; 34. } 35. //+------------------------------------------------------------------+
Control Indicator Snippet
Please note that only line 23 needed to be changed. Of course, I also changed the version number specified on line 6 and the link to this article given on line 7. Thus, the replay/simulation service has been updated once again and is ready for the next phase, in which we will add a position indicator to the replay/simulation system.
However, this update proved so useful that it became clear we could also make one more change to the mouse indicator. This change is optional: you are not required to implement it. However, the compiled code that you'll find in the attachments to this and subsequent articles will already include the update shown below. Please note that three complete files are provided here, as I will not explain in detail exactly where the changes were made.
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.132" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/13531" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. double GL_PriceClose; 13. datetime GL_TimeAdjust; 14. //+------------------------------------------------------------------+ 15. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 16. //+------------------------------------------------------------------+ 17. C_Study *Study = NULL; 18. //+------------------------------------------------------------------+ 19. input color user01 = clrBlack; //Price Line 20. input color user02 = clrPaleGreen; //Positive Study 21. input color user03 = clrLightCoral; //Negative Study 22. //+------------------------------------------------------------------+ 23. C_Study::eStatusMarket m_Status; 24. //+------------------------------------------------------------------+ 25. int OnInit() 26. { 27. Study = new C_Study(0, "Indicator Mouse Study", user01, user02, user03); 28. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 29. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 30. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 31. m_Status = C_Study::eCloseMarket; 32. 33. return INIT_SUCCEEDED; 34. } 35. //+------------------------------------------------------------------+ 36. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 37. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 38. const long& volume[], const int& spread[]) 39. { 40. GL_PriceClose = close[rates_total - 1]; 41. if (_Symbol == def_SymbolReplay) 42. GL_TimeAdjust = spread[rates_total - 1] & (~def_MaskTimeService); 43. (*Study).Update(m_Status); 44. 45. return rates_total; 46. } 47. //+------------------------------------------------------------------+ 48. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 49. { 50. (*Study).DispatchMessage(id, lparam, dparam, sparam); 51. 52. ChartRedraw(); 53. } 54. //+------------------------------------------------------------------+ 55. void OnBookEvent(const string &symbol) 56. { 57. MqlBookInfo book[]; 58. C_Study::eStatusMarket loc = m_Status; 59. 60. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 61. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 62. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : (symbol == def_SymbolReplay ? C_Study::eInReplay : C_Study::eInTrading)); 63. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 64. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 65. if (loc != m_Status) (*Study).Update(m_Status); 66. } 67. //+------------------------------------------------------------------+ 68. void OnDeinit(const int reason) 69. { 70. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 71. 72. delete Study; 73. } 74. //+------------------------------------------------------------------+
Mouse Indicator
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(0, m_Info.szBtn1, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - 18); 033. ObjectSetString(0, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo); 034. } 035. if (m_Info.bvD) 036. { 037. v1 = NormalizeDouble((((GetPositionsMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 038. ObjectSetInteger(0, m_Info.szBtn2, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - 1); 039. ObjectSetInteger(0, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 040. ObjectSetString(0, 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(0, m_Info.szBtn3, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - 1); 046. ObjectSetInteger(0, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 047. ObjectSetString(0, 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(0, CHART_EVENT_OBJECT_DELETE, false); 090. ObjectDelete(0, sz); 091. ChartSetInteger(0, 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. ZeroMemory(m_Info); 100. if (_LastError >= ERR_USER_ERROR_FIRST) return; 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(0); 165. } 166. //+------------------------------------------------------------------+ 167. }; 168. //+------------------------------------------------------------------+ 169. #undef def_ExpansionPrefix 170. #undef def_MousePrefixName 171. //+------------------------------------------------------------------+
C_Study
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. //+------------------------------------------------------------------+ 015. protected: 016. //+------------------------------------------------------------------+ 017. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 018. { 019. if (!m_Mem.IsInitOk) return; 020. CreateObjectGraphics(szName, OBJ_BUTTON); 021. ObjectSetInteger(0, szName, OBJPROP_STATE, true); 022. ObjectSetInteger(0, szName, OBJPROP_BORDER_COLOR, clrBlack); 023. ObjectSetInteger(0, szName, OBJPROP_COLOR, clrBlack); 024. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor); 025. ObjectSetString(0, szName, OBJPROP_FONT, "Lucida Console"); 026. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, 10); 027. ObjectSetInteger(0, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 028. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x); 029. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 030. ObjectSetInteger(0, szName, OBJPROP_XSIZE, w); 031. ObjectSetInteger(0, szName, OBJPROP_YSIZE, 18); 032. } 033. //+------------------------------------------------------------------+ 034. private : 035. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 036. struct st01 037. { 038. st_Mouse Data; 039. color corLineH, 040. corTrendP, 041. corTrendN; 042. eStudy Study; 043. }m_Info; 044. struct st_Mem 045. { 046. bool CrossHair, 047. IsFull, 048. IsInitOk; 049. datetime dt; 050. string szShortName, 051. szLineH, 052. szLineV, 053. szLineT, 054. szBtnS; 055. }m_Mem; 056. //+------------------------------------------------------------------+ 057. void GetDimensionText(const string szArg, int &w, int &h) 058. { 059. TextSetFont("Lucida Console", -100, FW_NORMAL); 060. TextGetSize(szArg, w, h); 061. h += 5; 062. w += 5; 063. } 064. //+------------------------------------------------------------------+ 065. void CreateStudy(void) 066. { 067. if (m_Mem.IsFull) 068. { 069. CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH); 070. CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH); 071. ObjectSetInteger(0, m_Mem.szLineT, OBJPROP_WIDTH, 2); 072. CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy); 073. } 074. m_Info.Study = eStudyCreate; 075. } 076. //+------------------------------------------------------------------+ 077. void ExecuteStudy(const double memPrice) 078. { 079. double v1 = GetPositionsMouse().Position.Price - memPrice; 080. int w, h; 081. 082. if (!CheckClick(eClickLeft)) 083. { 084. m_Info.Study = eStudyNull; 085. ChartSetInteger(0, CHART_MOUSE_SCROLL, true); 086. if (m_Mem.IsFull) ObjectsDeleteAll(0, def_MousePrefixName + "T"); 087. }else if (m_Mem.IsFull) 088. { 089. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 090. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetPositionsMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 091. GetDimensionText(sz1, w, h); 092. ObjectSetString(0, m_Mem.szBtnS, OBJPROP_TEXT, sz1); 093. ObjectSetInteger(0, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 094. ObjectSetInteger(0, m_Mem.szBtnS, OBJPROP_XSIZE, w); 095. ObjectSetInteger(0, m_Mem.szBtnS, OBJPROP_YSIZE, h); 096. ObjectSetInteger(0, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetPositionsMouse().Position.X_Adjusted - w); 097. ObjectSetInteger(0, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 098. ObjectMove(0, m_Mem.szLineT, 1, GetPositionsMouse().Position.dt, GetPositionsMouse().Position.Price); 099. ObjectSetInteger(0, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetPositionsMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 100. } 101. m_Info.Data.ButtonStatus = eKeyNull; 102. } 103. //+------------------------------------------------------------------+ 104. public : 105. //+------------------------------------------------------------------+ 106. C_Mouse(const long id, const string szShortName) 107. :C_Terminal(id) 108. { 109. m_Mem.IsInitOk = false; 110. m_Mem.szShortName = szShortName; 111. } 112. //+------------------------------------------------------------------+ 113. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 114. :C_Terminal(id) 115. { 116. if (!(m_Mem.IsInitOk = IndicatorCheckPass(m_Mem.szShortName = szShortName))) return; 117. m_Mem.CrossHair = (bool)ChartGetInteger(0, CHART_CROSSHAIR_TOOL); 118. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 119. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 120. ZeroMemory(m_Info); 121. m_Info.corLineH = corH; 122. m_Info.corTrendP = corP; 123. m_Info.corTrendN = corN; 124. m_Info.Study = eStudyNull; 125. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 126. CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH); 127. ChartRedraw(0); 128. } 129. //+------------------------------------------------------------------+ 130. ~C_Mouse() 131. { 132. long loc = ChartGetInteger(0, CHART_EVENT_OBJECT_DELETE); 133. if (!m_Mem.IsInitOk) return; 134. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 135. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(0, m_Mem.szShortName) != -1); 136. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 137. ObjectsDeleteAll(0, def_MousePrefixName); 138. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, loc); 139. } 140. //+------------------------------------------------------------------+ 141. inline bool CheckClick(const eBtnMouse value) 142. { 143. return (m_Info.Data.ButtonStatus & value) == value; 144. } 145. //+------------------------------------------------------------------+ 146. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 147. { 148. int w = 0; 149. static double memPrice = 0; 150. 151. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 152. switch (id) 153. { 154. case (CHARTEVENT_CUSTOM + evHideMouse): 155. if (m_Mem.IsFull) ObjectSetInteger(0, m_Mem.szLineH, OBJPROP_COLOR, clrNONE); 156. break; 157. case (CHARTEVENT_CUSTOM + evShowMouse): 158. if (m_Mem.IsFull) ObjectSetInteger(0, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH); 159. break; 160. case CHARTEVENT_MOUSE_MOVE: 161. m_Info.Data = GetPositionsMouse(); 162. if (m_Mem.IsFull) ObjectMove(0, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price); 163. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(0, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0); 164. m_Info.Data.ButtonStatus = (uchar) sparam; 165. if (CheckClick(eClickMiddle) && (m_Info.Study != eStudyCreate)) 166. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(0, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 167. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 168. { 169. ChartSetInteger(0, CHART_MOUSE_SCROLL, false); 170. if (m_Mem.IsFull) ObjectMove(0, m_Mem.szLineT, 0, m_Mem.dt = m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price); 171. m_Info.Study = eStudyExecute; 172. } 173. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 174. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 175. break; 176. case CHARTEVENT_OBJECT_DELETE: 177. if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH)) 178. CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH); 179. break; 180. } 181. } 182. //+------------------------------------------------------------------+ 183. }; 184. //+------------------------------------------------------------------+ 185. #undef macro_NameObjectStudy 186. //+------------------------------------------------------------------+
C_Mouse
With that, we have completely updated the system. Finally, we can begin working on the replay/simulation system so that we can use the position indicator with a custom symbol. This is the symbol used by the replay/simulation system.
Updating the Position Indicator Code
Very well. Now that we have updated all the systems, we can begin adapting the position indicator code for use in the replay/simulation system. To adapt the code, we'll start by assuming that we don't yet know how the SQL database will be created. The new, complete code is shown below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Positions.ico" 004. #property description "Indicator for tracking an open position on the server." 005. #property description "This should preferably be used together with an Expert Advisor." 006. #property description "For more details see the same article." 007. #property version "1.133" 008. #property link "https://www.mql5.com/pt/articles/13531" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. //+------------------------------------------------------------------+ 012. #define def_ShortName "Position View" 013. //+------------------------------------------------------------------+ 014. #include <Market Replay\Order System\C_ElementsTrade.mqh> 015. #include <Market Replay\Defines.mqh> 016. //+------------------------------------------------------------------+ 017. input ulong user00 = 0; //For use in an Expert Advisor 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szShortName, 023. szSymbol; 024. double priceOpen, 025. var, 026. tickSize; 027. char digits; 028. bool bIsBuy; 029. }m_Infos; 030. //+------------------------------------------------------------------+ 031. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 032. //+------------------------------------------------------------------+ 033. inline const double _PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE arg) 034. { 035. if (_Symbol != def_SymbolReplay) 036. return PositionGetDouble(arg); 037. 038. return 0; 039. } 040. //+------------------------------------------------------------------+ 041. inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg) 042. { 043. if (_Symbol != def_SymbolReplay) 044. return PositionGetInteger(arg); 045. 046. return 0; 047. } 048. //+------------------------------------------------------------------+ 049. inline const bool _PositionSelectByTicket(ulong arg) 050. { 051. if (_Symbol != def_SymbolReplay) 052. return PositionSelectByTicket(arg); 053. 054. return false; 055. } 056. //+------------------------------------------------------------------+ 057. bool CheckCatch(ulong ticket) 058. { 059. double vv; 060. 061. ZeroMemory(m_Infos); 062. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 063. if (!_PositionSelectByTicket(m_Infos.ticket)) return false; 064. if (ChartWindowFind(0, m_Infos.szShortName) >= 0) 065. { 066. m_Infos.ticket = 0; 067. return false; 068. } 069. m_Infos.szSymbol = (_Symbol == def_SymbolReplay ? def_SymbolReplay : PositionGetString(POSITION_SYMBOL)); 070. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 071. m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 072. vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 073. m_Infos.var = m_Infos.tickSize / vv; 074. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 075. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 076. 077. return true; 078. } 079. //+------------------------------------------------------------------+ 080. inline void ProfitNow(void) 081. { 082. double ask, bid, value; 083. 084. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 085. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 086. if (Open != NULL) 087. { 088. (*Open).ViewValue(value = (m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 089. (*Take).ViewValue(value, false); 090. (*Stop).ViewValue(value, false); 091. } 092. } 093. //+------------------------------------------------------------------+ 094. int OnInit() 095. { 096. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 097. if (!CheckCatch(user00)) 098. { 099. ChartIndicatorDelete(0, 0, def_ShortName); 100. return INIT_FAILED; 101. } 102. 103. return INIT_SUCCEEDED; 104. } 105. //+------------------------------------------------------------------+ 106. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 107. { 108. ProfitNow(); 109. 110. return rates_total; 111. } 112. //+------------------------------------------------------------------+ 113. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 114. { 115. double volume; 116. 117. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 118. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 119. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 120. switch (id) 121. { 122. case CHARTEVENT_CUSTOM + evUpdate_Position: 123. if (lparam != m_Infos.ticket) break; 124. if (!_PositionSelectByTicket(m_Infos.ticket)) 125. { 126. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 127. return; 128. }; 129. if (Open == NULL) Open = new C_ElementsTrade( 130. m_Infos.ticket, 131. m_Infos.szSymbol, 132. evMsgClosePositionEA, 133. clrRoyalBlue, 134. m_Infos.digits, 135. m_Infos.tickSize, 136. StringFormat("%I64u : Position opening price.", m_Infos.ticket), 137. m_Infos.bIsBuy = (_PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) 138. ); 139. if (Take == NULL) Take = new C_ElementsTrade( 140. m_Infos.ticket, 141. m_Infos.szSymbol, 142. evMsgCloseTakeProfit, 143. clrForestGreen, 144. m_Infos.digits, 145. m_Infos.tickSize, 146. StringFormat("%I64u : Take Profit price.", m_Infos.ticket), 147. m_Infos.bIsBuy 148. ); 149. if (Stop == NULL) Stop = new C_ElementsTrade( 150. m_Infos.ticket, 151. m_Infos.szSymbol, 152. evMsgCloseStopLoss, 153. clrFireBrick, 154. m_Infos.digits, 155. m_Infos.tickSize, 156. StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), 157. m_Infos.bIsBuy 158. ); 159. volume = _PositionGetDouble(POSITION_VOLUME); 160. (*Open).UpdatePrice(0, m_Infos.priceOpen = _PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 161. (*Take).UpdatePrice(m_Infos.priceOpen, _PositionGetDouble(POSITION_TP), volume, m_Infos.var, _PositionGetDouble(POSITION_SL)); 162. (*Stop).UpdatePrice(m_Infos.priceOpen, _PositionGetDouble(POSITION_SL), volume, m_Infos.var, _PositionGetDouble(POSITION_TP)); 163. ProfitNow(); 164. break; 165. } 166. ChartRedraw(); 167. }; 168. //+------------------------------------------------------------------+ 169. void OnDeinit(const int reason) 170. { 171. delete Open; 172. delete Take; 173. delete Stop; 174. } 175. //+------------------------------------------------------------------+
Position Indicator
I know it might seem crazy to paste the entire code again. However, I want you to look carefully at what I am doing, because this is extremely important and may come in handy for you in the future. Take a look at the previous code. I have added three new functions whose purpose is to let us create everything needed in the Expert Advisor so that the Expert Advisor itself builds a database of the positions that arise. These positions will be created as the replay/simulation system runs, when you use the Chart Trade indicator and the mouse indicator to send buy or sell requests to the Expert Advisor. The rest of the interaction will be handled through custom events triggered by the Expert Advisor. These events will be used to update, place, or remove the position indicator on the chart of the replay/simulation symbol.
All right. The main purpose of these three functions, located on lines 33, 41, and 49, is to ensure that the position indicator works the same way in any scenario. If we are connected to a real trading server—through a demo account or a live account—the checks on lines 35, 43, and 51 will allow the native MQL5 functions to retrieve and update position values so that the data is displayed correctly on the chart. And don't think I've forgotten about PositionGetString. Take a look at line 69, and you'll see how I solved the problem of accessing PositionGetString. Since this is a very simple matter, there was no need to create an additional procedure.
This is the first part of what we need. Now note that the names of the functions on those lines are very similar to those used in the MQL5 library. The similarity in the names is intentional, since you probably won't find any differences when reading the code. However, if you look closely, you'll notice that the names of the functions used to retrieve position data differ slightly in their first character. Thus, we can reduce all the necessary work to implementing the code contained in these modified functions.
Naturally, many people would interpret this programming approach as function overloading. However, in this case, it is not function overloading in the strict sense of the word, since the names differ slightly.
With that, we have completed making the necessary changes to the position indicator. Now we need to move on to the Expert Advisor, where we'll make some more significant changes.
Strength in Unity
For a while, we'll be focusing on the Expert Advisor. Judging by the comments, I know that many people are following this series so they can use their own Expert Advisor. So I'm going to show you how to integrate your code with what we'll be developing here. The goal is for the replay/simulation system to be useful to you in practice, since there is no point in limiting yourself solely to what I explain here. For this reason, we will not place the code directly in the Expert Advisor. We'll create a header file to hold the code, although we may need more than one.
In the first step, we'll create a file that will provide us with initial support for working with SQLite. I've already explained how to do some of these things in other articles, but here we'll look at something much more specific. The source code was already presented in another article in this same series, where I discussed SQL, so I won't explain it again. I'll simply show it in its original state. You can see it in full below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_FileDataBase "Replay.db" 005. //+------------------------------------------------------------------+ 006. #include "..\Service Graphics\Support\C_Array.mqh" 007. //+------------------------------------------------------------------+ 008. class C_ReplayDataBase 009. { 010. private : 011. //+------------------------------------------------------------------+ 012. struct st_00 013. { 014. int handle, 015. request; 016. }m_Infos; 017. C_Array m_Arr; 018. //+------------------------------------------------------------------+ 019. void Convert(const char &buff[], const int size) 020. { 021. string sz0 = ""; 022. bool b0, b1, bs1, bs2, bc0, bc1, bc; 023. int nLine = 1; 024. 025. b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false; 026. for (int count = 0, nC0 = nLine; count < size; count++) 027. { 028. switch (buff[count]) 029. { 030. case '\t': 031. sz0 += (bs1 || bs2 ? "\t" : ""); 032. break; 033. case '\n': 034. nC0++; 035. case '\r': 036. bc0 = false; 037. break; 038. case ';': 039. b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true); 040. default: 041. switch (buff[count]) 042. { 043. case '"': 044. bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1); 045. break; 046. case '\'': 047. bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2); 048. break; 049. } 050. if (((count + 1) < size) && (!bs1) && (!bs2)) 051. { 052. if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true; 053. if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true; 054. if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false; 055. if (bc) 056. { 057. count += 1; 058. bc = false; 059. continue; 060. } 061. } 062. if (!(bc0 || bc1)) 063. { 064. if ((!b1) && (buff[count] > ' ')) 065. { 066. b1 = true; 067. nLine = nC0; 068. } 069. sz0 += (b1 ? StringFormat("%c", buff[count]) : ""); 070. } 071. } 072. if (b0) 073. { 074. m_Arr.Add(sz0, nLine); 075. sz0 = ""; 076. b0 = b1 = false; 077. } 078. } 079. } 080. //+------------------------------------------------------------------+ 081. const string ExecSQL(void) 082. { 083. string szCmd; 084. 085. for (int count = 0, nLine; count >= 0; count++) 086. { 087. szCmd = m_Arr.At(count, nLine); 088. if (nLine < 0) break; 089. if (!ExecCommandSQL(szCmd)) 090. return StringFormat("Execution of line %d of the SQL script failed...", nLine); 091. } 092. 093. return NULL; 094. } 095. //+------------------------------------------------------------------+ 096. public : 097. //+------------------------------------------------------------------+ 098. C_ReplayDataBase() 099. { 100. ZeroMemory(m_Infos); 101. m_Infos.handle = DatabaseOpen(def_FileDataBase, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE); 102. m_Infos.request = INVALID_HANDLE; 103. } 104. //+------------------------------------------------------------------+ 105. ~C_ReplayDataBase() 106. { 107. DatabaseClose(m_Infos.handle); 108. } 109. //+------------------------------------------------------------------+ 110. const string ExecResourceSQL(const string szResource) 111. { 112. char buff[]; 113. int size; 114. 115. ArrayResize(buff, size = StringLen(szResource)); 116. StringToCharArray(szResource, buff); 117. Convert(buff, size); 118. ArrayFree(buff); 119. 120. return ExecSQL(); 121. } 122. //+------------------------------------------------------------------+ 123. bool ExecCommandSQL(const string szCmd) 124. { 125. return (m_Infos.handle == INVALID_HANDLE ? false : DatabaseExecute(m_Infos.handle, szCmd)); 126. } 127. //+------------------------------------------------------------------+ 128. bool ExecRequestOfData(const string szCmd) 129. { 130. if (m_Infos.request != INVALID_HANDLE) DatabaseFinalize(m_Infos.request); 131. return ((m_Infos.request = DatabasePrepare(m_Infos.handle, szCmd)) != INVALID_HANDLE); 132. } 133. //+------------------------------------------------------------------+ 134. template <typename T> bool GetRegisterOfRequest(T &stObject, const bool Finish = true) 135. { 136. if (DatabaseReadBind(m_Infos.request, stObject)) return true; 137. if (Finish) 138. { 139. DatabaseFinalize(m_Infos.request); 140. m_Infos.request = INVALID_HANDLE; 141. } 142. return false; 143. } 144. //+------------------------------------------------------------------+ 145. }; 146. //+------------------------------------------------------------------+ 147. #undef def_FileDataBase 148. //+------------------------------------------------------------------+
C_ReplayDataBase
Although this code is slightly different from the original, it works in exactly the same way. It is a little simpler because here we are creating something more specific, different from what we did before. Nevertheless, everything that was said about this code in the article where it first appeared still holds true. The only significant difference is on line 4, where we specify the name of the database file.
All right. Now we can start using the SQLite database. Now we need to prepare the Expert Advisor to generate the data that the position indicator will use. But before we get started, we need to go over a few details. To understand this, consider the skeleton of the Expert Advisor we will use, presented as a code snippet. The code is shown below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Replay - EA.ico" 004. #property description "Demo version between interaction" 005. #property description "of Chart Trade and Expert Advisor" 006. #property version "1.133" 007. #property link "https://www.mql5.com/pt/articles/13531" 008. //+------------------------------------------------------------------+ 009. #include <Market Replay\Order System\C_Orders.mqh> 010. #include <Market Replay\Auxiliar\C_Terminal.mqh> 011. #include <Market Replay\SQL\C_ReplayDataBase.mqh> 012. //+------------------------------------------------------------------+ 013. enum eTypeContract {MINI, FULL}; 014. //+------------------------------------------------------------------+ 015. input eTypeContract user00 = MINI; //Cross Order by Contract Type 016. //+------------------------------------------------------------------+ 017. C_Orders *Orders = NULL; 018. C_Terminal *Terminal = NULL; 019. C_ReplayDataBase *DB = NULL; 020. //+------------------------------------------------------------------+ 021. int OnInit() 022. { 023. if (_Symbol == def_SymbolReplay) DB = new C_ReplayDataBase(); 024. Orders = new C_Orders(0xC0DEDAFE78514269); 025. 026. return INIT_SUCCEEDED; 027. } 028. //+------------------------------------------------------------------+ 029. void OnTick() {} 030. //+------------------------------------------------------------------+ 031. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 032. { 033. int handle; 034. 035. (*Orders).DispatchMessage(id, lparam, dparam, sparam); . . . 061. } 062. //+------------------------------------------------------------------+ 063. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) 064. { . . . 092. } 093. //+------------------------------------------------------------------+ 094. void OnDeinit(const int reason) 095. { . . . 111. delete Orders; 112. delete Terminal; 113. delete DB; 114. } 115. //+------------------------------------------------------------------+
Expert Advisor Snippet
Here, I'd like you to pay attention to a few details. On line 11, we add the header file we just discussed; it is needed so that we can use the SQLite database. On line 19, we create a pointer to the database access class. On line 23, we specify when the database will be used—or, more precisely, when the pointer variable will gain access to the class that manages the database. Finally, on line 113, we free the memory allocated to the pointer. Essentially, that's what we'll be doing. However, there are some differences between using an Expert Advisor when it is connected to a live trading server—whether through a demo account or a live account—and using it in a replay/simulation system.
When the Expert Advisor is connected to a real trading server, all access to position data will be handled through the OnTradeTransaction procedure. However, when using a replay/simulation system, we won't be able to work the same way.
This is precisely where an important issue arises that we need to analyze and resolve. When using the replay/simulation system, we could place the entire load on the Expert Advisor. But isn't there a simpler way to handle this situation? When we click one of the Chart Trade buttons, a custom event will be triggered. Line 35 in the code above will catch this event and cause the C_Orders class to execute the request. However, simply sending a request is not enough for the position indicator to appear or be updated. The OnTradeTransaction procedure is responsible for displaying or updating the position indicator. For this reason, we have two scenarios: in one, we are connected to a real trading server, and in the other, we are not.
However, if we limit the operation of line 35 so that the C_Orders class does not receive events when using the replay/simulation system, we will partially reduce the load on the Expert Advisor. In that case, we could simulate the server's response—that is, the event that MetaTrader 5 generates when it receives information from the server. This event triggers the execution of OnTradeTransaction, which allows you to add, update, or remove a position indicator from the chart.
Keep the following in mind: when using a replay/simulation system, the information that the position indicator will use will be stored in the database. Thus, we ensure that it works reliably in either scenario. We will simulate not only price movement, as we already do, but also the events typically generated by MetaTrader 5.
Although we still have one problem to solve, we'll leave that issue for later. First, we need to make the Expert Advisor explicitly simulate the OnTradeTransaction event. I realize that forcibly simulating this event may seem like an extremely difficult task, but it’s actually not that complicated. Furthermore, by following this approach, we’ll need to implement much less code, and we’ll end up with a much more stable and reliable Expert Advisor, since it will be able to process both events coming from a real trading server and events simulated by the replay/simulation system in the same way.
Concluding Thoughts
As you just read in the previous section, we're entering the home stretch. The development of the replay/simulation system is nearly complete. Of course, we still have a few things to refine, but compared to everything we've already accomplished, implementing what remains will not be difficult. However, since everything presented in this article needs to be properly absorbed and understood, I want you, as a reader and enthusiast, to start thinking about how to adapt your Expert Advisor to what we’ll be presenting in the next articles. This way, you'll be able to properly integrate your code with what we'll be discussing and implementing.
I don't know what level of knowledge or experience each reader has with MQL5. However, don't wait until too much has piled up before you start thinking about how to adapt your code to what will be implemented in this series of articles. If you wait too long, you'll end up losing the thread, and when you try to perform the integration, you'll face serious difficulties, at the very least. This is even more likely because I know that many people prefer certain approaches to programming. However, I don't intend to create a one-size-fits-all solution here. My goal is to show how to develop a full-fledged replay/simulation system whose applications can also be used on both a demo account and a live account. So get ready, because the next few articles will be more challenging, and in them we'll finish developing the replay/simulation system.
| File | Description |
|---|---|
| Experts\Expert Advisor.mq5 | Shows interaction between Chart Trade and the Expert Advisor. Mouse Study is required for interaction. |
| Indicators\Chart Trade.mq5 | Creates a window in which the order to be sent is configured. Mouse Study is required for interaction. |
| Indicators\Market Replay.mq5 | Creates the necessary controls for interacting with the replay/simulation service. Mouse Study is required for interaction. |
| Indicators\Mouse Study.mq5 | Provides interaction between the graphical controls and the user. This is necessary both for using the replay/simulation system and for trading in the real market. |
| Indicators\Order Indicator.mq5 | Displays market orders and allows users to interact with and manage them. |
| Indicators\Position View.mq5 | Displays market positions and allows users to interact with and manage them. |
| Services\Market Replay.mq5 | Creates and maintains the market replay/simulation service. This is the main file of the entire system. |
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/13531
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
From Basic to Intermediate: Operator Overloading (III)
Building a Session Performance Analytics Dashboard in MQL5
Features of Experts Advisors
Defining your Edge (Part 6): Harnessing Fourier Transform and a Spiking Neural Network in an Expert Advisor
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use