Моделирование рынка: В единстве — сила (I)
Введение
Всем привет и добро пожаловать в новую статью из серии, посвящённой созданию системы репликации/моделирования.
В предыдущей статье «Моделирование рынка: Position View (XX)» я показал, как изменить индикатор позиции, чтобы создать тень на уровне, где находилась цена. Однако у этой реализации есть небольшой недостаток. Проблема возникает, когда вы перетаскиваете тейк-профит или стоп-лосс и решаете изменить режим отображения результата сделки, например, с денежного выражения на процентное. Хотя некоторые трейдеры, возможно, захотят сделать это во время перемещения, такой возможности нет. Сначала необходимо завершить перемещение и только после этого изменить режим отображения данных. После этого потребуется снова начать перемещение, чтобы информация отображалась должным образом.
Хотя решить эту проблему было бы довольно просто, лично я не считаю нужным ни решать её, ни показывать, как это сделать. Думаю, те, кто следит за этой серией, уже, вероятно, хотят поскорее увидеть готовую систему. По этой причине в данной статье было решено сразу перейти к этапу, на котором мы интегрируем четыре компонента в систему репликации/моделирования, поскольку они уже достаточно проработаны для использования в этой системе.
Очевидно, что систему отложенных ордеров ещё предстоит разработать. Однако, поскольку эта система широко использует индикатор позиции и требует лишь некоторых изменений в нём, мы создадим систему отложенных ордеров непосредственно в системе репликации/моделирования.
После принятия этого решения в данной статье мы внесём соответствующие изменения, чтобы начать использовать индикатор позиции в системе репликации/моделирования, по крайней мере в его самом базовом виде. Итак, давайте приступим к делу и начнём вносить изменения.
Разберёмся, что у нас есть на данный момент
Для начала я хочу, чтобы вы посмотрели, как теперь выглядит код класса C_ElementsTrade после удаления дублирующегося кода, который был в предыдущей статье. Полный код вы можете увидеть ниже.
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
Думаю, у вас не возникнет трудностей с пониманием этого кода, так как он полностью соответствует тому, что мы рассматривали до сих пор. Однако здесь весь дублирующийся код был удалён и заменён макросами. Таким образом, хотя для компилятора код по-прежнему остается дублирующимся, для нас, поскольку нам нужно читать и изменять код, это дублирование на практике перестает существовать. Если мы изменим код макроса, нам нужно будет внести это изменение только один раз.
Чтобы лучше понять такое использование макросов, обратите внимание на строку 66, где определяется макрос. Этот макрос имеет локальную область действия, поэтому он удаляется в строке 101. Я хочу, чтобы вы обратили внимание на то, что мы используем макрос в двух местах: в строках 90 и 98. В этих местах компилятор берёт код макроса и вставляет его напрямую. Таким образом, с точки зрения компилятора код действительно дублируется, но для нас — нет. Если вы захотите изменить начальную позицию по координате X, достаточно будет внести это изменение только один раз — а именно в строке 68. Это изменение отразится как на тени, так и на отображаемом сегменте. Вот так просто.
Обратите также внимание, что, поскольку мы устранили любую зависимость от символа и любую связанную с ним информацию, нам больше не нужно беспокоиться о классе C_ElementsTrade при переносе индикатора в систему репликации/моделирования. С этого момента мы сосредоточим всё внимание на основном коде индикатора, который полностью приведён ниже. Имейте в виду, что речь идет о предыдущей версии — той самой, которую мы рассматривали до прошлой статьи.
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; //Для использования в советнике 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. //+------------------------------------------------------------------+
Индикатор позиции
Как вы можете видеть, здесь находится весь набор зависимостей. Этот код подробно объяснялся в предыдущих статьях, пока не достиг своего нынешнего состояния. С этого момента все изменения будут вноситься в показанный выше код. Цель состоит в том, чтобы изолировать или, точнее говоря, отделить систему репликации/моделирования от реального рынка. Однако, даже если мы проведём это разделение, индикатор позиции останется готовым к работе в обеих ситуациях. Сам индикатор не сможет определить, работает ли он в системе репликации/моделирования или подключён к реальному торговому серверу — будь то через демо-счёт или реальный счёт.
Прежде всего необходимо получить имя символа, который мы будем использовать в системе репликации/моделирования. Так мы начнём разделять эти вещи. Это имя объявлено в заголовочном файле Defines.mqh. Обратитесь к предыдущим статьям, чтобы узнать подробности. Нам нужно лишь использовать определение из этого файла, который уже подключён в строке 15 основного кода.
На первый взгляд эта задача кажется относительно простой, не так ли? Однако она оказывается гораздо более трудоёмкой, чем может показаться. Причина в том, что все обращения к PositionGetDouble, PositionGetInteger и PositionSelectByTicket являются точками блокировки для использования системы репликации/моделирования. Именно эти препятствия нам необходимо обойти, чтобы индикатор позиции действительно можно было использовать в системе репликации/моделирования.
Поэтому нам нужно найти решение, которое позволит нам начать работать эффективно. На этом этапе многие застрянут, не сумев перейти к следующему шагу реализации, поскольку это кажется невозможным. Но так ли это на самом деле? Напомню кое-что из того, что я некоторое время назад объяснял в этой же серии статей: использование SQL и взаимодействие между MetaTrader 5 и Excel. Когда я затрагивал эти темы, вы, возможно, думали, что они не имеют никакой пользы в рамках серии, посвящённой созданию системы репликации/моделирования. Зачем объяснять это в данном контексте? Что ж, если вы не изучали эти материалы, рекомендую найти их в этой серии и пересмотреть, потому что пришло время применить эти знания.
Понять выбор решения
По практическим соображениям, и не обязательно из-за сложностей программирования, мы будем использовать в системе репликации/моделирования решение на основе SQL. Дело в том, что MQL5 уже предоставляет средства для работы с SQL без необходимости прибегать к внешнему программированию. Помните, что моя цель — показать, что мы можем создать систему репликации/моделирования полностью на MQL5. Тем не менее, если вам это нужно или вы так предпочитаете, вы можете адаптировать всё так, чтобы использовать Excel вместе с MetaTrader 5 и таким образом получать метрики своих операций и исследований в системе репликации/моделирования. Однако я не буду здесь объяснять, как это делается. Мы будем напрямую использовать базу данных SQL.
Здесь может возникнуть вопрос: почему бы не использовать отдельный файл — текстовый или двоичный — для имитации сервера? Разве это не было бы проще, чем использовать базу данных SQL? Это уместный вопрос, и, в принципе, да, это было бы проще. Однако учтите следующее: если операции, выполняемые в системе репликации/моделирования, сохранялись бы в файле, созданном исключительно для этой цели, то впоследствии нам пришлось бы проделать дополнительную работу для их анализа. Если хранить всё в базе данных SQL, необходимость в этой дополнительной работе отпадает. Гораздо проще прочитать содержимое базы данных SQL, чем преобразовывать содержимое файла, специально созданного для хранения операций, выполненных в системе репликации/моделирования.
Таким образом, вопрос не в использовании внутри системы репликации/моделирования, а в том, насколько легко впоследствии прочитать то, что было сделано в системе репликации/моделирования. Именно поэтому мы будем использовать базу данных SQL.
С этого момента всё пойдёт немного в другом направлении. Индикатор позиции не должен создавать файл базы данных; он должен только считывать этот файл. Создание файла будет возложено на советник, который и будет фактически моделировать торговый сервер. Индикатор позиции будет только отображать данные, содержащиеся в базе данных, созданной советником.
Хорошо. Это исходная основа. Однако, прежде чем начать, мы должны рассмотреть некоторые моменты. С момента последнего обновления сервиса репликации/моделирования вспомогательные классы претерпели важные изменения. Это одна из проблем, затрудняющих разработку более крупных проектов. Многие начинающие программисты в итоге просто бросают такие проекты. Другие, хотя и не сдаются, попадают в замкнутый круг, который мешает им по-настоящему вернуться к исходному проекту.
Если вы попытаетесь воспользоваться сервисом репликации/моделирования сейчас, спустя столько времени, то заметите, что исполняемый файл работает не так, как должен. И если вы заново скомпилируете сервис на основе старого кода, то убедитесь, что запустить воспроизведение системы невозможно. Даже если вам удастся принудительно запустить воспроизведение, вы не сможете взаимодействовать с индикатором управления. Это связано с тем, что в коде системы репликации/моделирования есть несколько устаревших элементов. Поэтому, прежде чем продолжить, нам нужно обновить старый код системы репликации/моделирования. Нет причин для беспокойства: обновление будет выполнено простым и понятным способом.
Обновление системы репликации/моделирования
Первое обновление нужно выполнить в классе C_DrawImage, как показано в следующем фрагменте.
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. //+------------------------------------------------------------------+
Фрагмент из C_DrawImage
Изменение находится в строке 141 и задаёт значение свойства OBJPROP_ZORDER. Без этой настройки объекты, созданные этим классом, имели бы то же значение ZORDER, что и объекты класса C_Mouse. Поскольку C_DrawImage используется в индикаторе управления, необходимо увеличить значение OBJPROP_ZORDER, чтобы класс C_Controls мог корректно перехватывать клики по объектам.
Следующее обновление потребует немного больше работы, поскольку оно затрагивает класс C_Controls. Хотя многие могут подумать, что эти изменения не нужны, на самом деле MetaTrader 5 за это время тоже получил множество обновлений и улучшений. Эти обновления и улучшения повысили стабильность программы, а также улучшили многие другие аспекты. Поэтому нам необходимо обновить систему репликации/моделирования. Тем не менее, я полагаю, что это будет последний раз, когда нам понадобится обновить систему репликации/моделирования. Ниже вы можете увидеть новый, уже обновлённый код класса C_Controls.
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
Я не буду вдаваться в подробности изменений. Это останется упражнением для вас: изучите код и попробуйте определить, что именно было изменено. Однако я дам вам подсказку: обратите внимание на конструктор в строке 138 и объявление класса в строке 27. Оба были изменены, как и некоторые другие части кода. Все эти изменения связаны с изменением в наследовании. Поскольку конструктор изменился, основной код тоже изменился. Ниже показан только тот фрагмент, который действительно пришлось обновить.
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. //+------------------------------------------------------------------+
Фрагмент индикатора управления
Обратите внимание, что нужно было изменить только строку 23. Конечно, я также изменил версию, указанную в строке 6, и ссылку на эту статью, приведённую в строке 7. Таким образом, сервис репликации/моделирования снова обновлён и готов к следующему этапу, на котором мы добавим индикатор позиции в систему репликации/моделирования.
Однако это обновление оказалось настолько полезным, что стало ясно: можно также внести ещё одно изменение в индикатор мыши. Это изменение необязательно: вы не обязаны его применять. Тем не менее скомпилированный код, который вы найдёте во вложениях к этой и следующим статьям, уже будет включать обновление, показанное ниже. Обратите внимание, что здесь представлены три полных файла, так как я не буду подробно объяснять, где именно были внесены изменения.
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. //+------------------------------------------------------------------+
Индикатор мыши
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
На этом мы полностью обновили систему. Наконец мы можем приступить к работе над системой репликации/моделирования, чтобы использовать индикатор позиции с пользовательским символом. Именно этот символ использует система репликации/моделирования.
Обновление кода индикатора позиции
Очень хорошо. Теперь, когда мы уже обновили все системы, можем приступить к адаптации кода индикатора позиции для использования в системе репликации/моделирования. Чтобы адаптировать код, вначале будем исходить из предположения, что мы пока ещё не знаем, как будет создаваться база данных SQL. Ниже приведён новый полный код.
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; //Для использования в советнике 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. //+------------------------------------------------------------------+
Индикатор позиции
Я знаю, что может показаться безумием снова вставлять весь код целиком. Однако я хочу, чтобы вы внимательно посмотрели на то, что я делаю, потому что это чрезвычайно важно и может пригодиться вам в будущем. Обратите внимание на предыдущий код. Я добавил три новые функции, цель которых — позволить нам создать в советнике всё необходимое, чтобы сам советник формировал базу данных с возникающими позициями. Эти позиции будут создаваться по мере работы системы репликации/моделирования, когда вы с помощью индикатора Chart Trade и индикатора мыши будете отправлять советнику запросы на покупку или продажу. Остальное взаимодействие будет осуществляться с помощью пользовательских событий, запускаемых советником. Эти события будут служить для обновления, размещения или удаления с графика индикатора позиции на символе репликации/моделирования.
Хорошо. Основная цель этих трёх функций, расположенных в строках 33, 41 и 49, — сделать так, чтобы индикатор позиции работал одинаково в любом сценарии. Если мы подключены к реальному торговому серверу — через демо-счёт или реальный счёт, — проверки в строках 35, 43 и 51 позволят исходным функциям MQL5 получать и обновлять значения позиции, чтобы данные корректно отображались на графике. И не думайте, что я забыл о PositionGetString. Посмотрите на строку 69, и вы увидите, как я решил проблему доступа к PositionGetString. Поскольку речь идёт об очень простой вещи, создавать дополнительную процедуру не потребовалось.
Это первая часть того, что нам нужно. Обратите теперь внимание на то, что имена функций, расположенных в упомянутых строках, очень похожи на те, что используются в библиотеке MQL5. Сходство названий намеренное, поскольку при чтении кода вы, по-видимому, не найдёте никаких различий. Однако, если присмотреться, можно заметить, что названия функций, предназначенных для получения данных о позиции, слегка различаются первым символом. Таким образом, мы можем свести всю необходимую работу к реализации кода, содержащегося в этих изменённых функциях.
Естественно, многие интерпретировали бы такой способ программирования как перегрузку функций. Однако в данном случае это не перегрузка в прямом смысле слова, поскольку имена немного различаются.
На этом мы завершили внесение необходимых изменений в индикатор позиции. Теперь нам нужно перейти к советнику, где мы внесём несколько более существенные изменения.
В единстве — сила
Некоторое время мы будем сосредоточены на советнике. Судя по комментариям, я знаю, что многие следят за этой серией, чтобы использовать своего собственного советника. Поэтому я покажу вам, как интегрировать ваш код с тем, что мы будем разрабатывать здесь. Цель состоит в том, чтобы система репликации/моделирования была полезна вам на практике, поскольку не имеет смысла ограничиваться исключительно тем, что я объясняю здесь. По этой причине мы не будем размещать код непосредственно в советнике. Мы создадим заголовочный файл для размещения кода, хотя, возможно, потребуется и не один.
На первом этапе мы создадим файл, который обеспечит нам начальную поддержку для работы с SQLite. Я уже объяснял, как делать некоторые из этих вещей, в других статьях, но здесь мы рассмотрим нечто гораздо более конкретное. Исходный код уже был представлен в другой статье из этой же серии, где я рассказывал о SQL, поэтому я не буду снова его объяснять. Я ограничусь тем, что покажу его исходное состояние. Ниже вы можете увидеть его полностью.
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
Хотя этот код немного отличается от исходного, работает он точно так же. Он немного проще, потому что здесь мы создаём нечто более конкретное, отличное от того, что делали ранее. Тем не менее всё, что было сказано об этом коде в статье, где он впервые появился, по-прежнему остаётся в силе. Единственное существенное отличие находится в строке 4, где мы задаём имя файла базы данных.
Хорошо. Теперь мы можем начать использовать базу данных SQLite. Теперь нам нужно подготовить советник к созданию данных, которые будет использовать индикатор позиции. Но прежде чем начать, нам нужно разобраться в некоторых деталях. Чтобы понять это, рассмотрите каркас советника, который мы будем использовать, представленный в виде фрагмента. Код приведён ниже.
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; //Перекрёстный ордер в контракте 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. //+------------------------------------------------------------------+
Фрагмент советника
Здесь я хочу, чтобы вы обратили внимание на некоторые детали. В строке 11 мы добавляем заголовочный файл, который только что рассмотрели; он нужен для того, чтобы мы могли использовать базу данных SQLite. В строке 19 мы создаём указатель для доступа к классу доступа к базе данных. В строке 23 мы определяем, когда будет использоваться база данных, а точнее, когда переменная-указатель получит доступ к классу, управляющему базой данных. Наконец, в строке 113 мы освобождаем память, занимаемую указателем. По сути, это то, что мы будем делать. Тем не менее, есть некоторые различия между использованием советника, когда он подключён к реальному торговому серверу — через демо-счёт или реальный счёт, — и его использованием в системе репликации/моделирования.
Когда советник подключён к реальному серверу, весь доступ к данным позиции будет осуществляться через процедуру OnTradeTransaction. Однако при использовании системы репликации/моделирования мы не сможем работать таким же образом.
И именно здесь возникает важный момент, который нам нужно проанализировать и решить. При использовании системы репликации/моделирования мы могли бы полностью возложить всю нагрузку на советник. Но разве нет более простого способа справиться с этой ситуацией? Когда мы нажмём одну из кнопок Chart Trade, сработает пользовательское событие. Строка 35 в приведённом выше коде перехватит это событие и заставит класс C_Orders выполнить запрос. Однако одной отправки запроса недостаточно для того, чтобы индикатор позиции появился или обновился. За появление или обновление индикатора позиции отвечает процедура OnTradeTransaction. По этой причине у нас есть два сценария: в одном мы подключены к реальному серверу, а в другом такого подключения нет.
Однако если мы ограничим работу строки 35, чтобы класс C_Orders не получал события при использовании системы репликации/моделирования, то частично снизим нагрузку на советник. В таком случае мы могли бы имитировать ответ сервера, то есть событие, которое MetaTrader 5 генерирует при получении информации от сервера. Это событие вызывает выполнение OnTradeTransaction, что позволяет разместить, обновить или удалить с графика индикатор позиции.
Помните следующее: при использовании системы репликации/моделирования информация, которую будет использовать индикатор позиции, будет храниться в базе данных. Таким образом, мы гарантируем безупречную работу в любом случае. Мы будем моделировать не только движение цены, как уже делаем, но и события, которые обычно генерирует MetaTrader 5.
Хотя нам ещё предстоит решить одну проблему, этот вопрос мы оставим на потом. Сначала нужно добиться, чтобы советник принудительно имитировал событие OnTradeTransaction. Я понимаю, что принудительное моделирование этого события может показаться чрезвычайно сложной задачей, но на самом деле всё не так уж сложно. Кроме того, следуя этому подходу, нам потребуется реализовать гораздо меньше кода, и мы получим гораздо более стабильный и надёжный советник, поскольку он сможет одинаково обрабатывать как события, поступающие с реального сервера, так и события, имитируемые системой репликации/моделирования.
Заключительные замечания
Как вы только что прочитали в предыдущей теме, мы выходим на финишную прямую. Разработка системы репликации/моделирования почти завершена. Конечно, нам ещё предстоит доработать некоторые моменты, но по сравнению со всем тем, что мы уже сделали, реализовать оставшееся будет несложно. Однако, поскольку всё показанное в этой статье нужно должным образом усвоить и понять, я хочу, чтобы вы, уважаемый читатель и энтузиаст, начали думать о том, как адаптировать свой советник к тому, что мы представим в следующих статьях. Таким образом, вы сможете правильно интегрировать свой код с тем, что мы будем рассматривать и реализовывать.
Я не знаю, каков уровень знаний или опыт работы с MQL5 у каждого читателя. Тем не менее, не ждите, пока накопится слишком много всего, прежде чем начать задумываться о том, как адаптировать свой код к тому, что будет реализовано в этой серии статей. Если вы будете слишком долго ждать, то в итоге потеряете нить, и, когда попытаетесь выполнить интеграцию, столкнётесь как минимум с серьёзными трудностями. Это будет ещё вероятнее, потому что я знаю, что многие предпочитают определённые подходы к программированию. Однако я не собираюсь создавать здесь универсальное решение. Моя цель — показать, как можно разработать полноценную систему репликации/моделирования, приложения которой также можно использовать как на демо-счёте, так и на реальном счёте. Так что приготовьтесь, потому что следующие статьи будут более сложными, и в них мы завершим разработку системы репликации/моделирования.
| Файл | Описание |
|---|---|
| Experts\Expert Advisor.mq5 | Показывает взаимодействие между Chart Trade и советником. Для взаимодействия необходим Mouse Study. |
| Indicators\Chart Trade.mq5 | Создаёт окно, в котором настраивается ордер, который будет отправлен. Для взаимодействия необходим Mouse Study. |
| Indicators\Market Replay.mq5 | Создаёт необходимые элементы управления для взаимодействия с сервисом репликации/моделирования. Для взаимодействия необходим Mouse Study. |
| Indicators\Mouse Study.mq5 | Обеспечивает взаимодействие между графическими элементами управления и пользователем. Это необходимо как для использования системы репликации/моделирования, так и для торговли на реальном рынке. |
| Indicators\Order Indicator.mq5 | Отвечает за отображение рыночных ордеров и позволяет взаимодействовать с ними и управлять ими. |
| Indicators\Position View.mq5 | Отвечает за отображение рыночных позиций и позволяет взаимодействовать с ними и управлять ими. |
| Services\Market Replay.mq5 | Создаёт и поддерживает работу сервиса репликации/моделирования рынка. Это главный файл всей системы. |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/13531
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: Перегрузка операторов (III)
Алгоритм оптимизации на основе приспособленности — Fitness Dependent Optimizer (FDO)
Причинный граф как признак машинного обучения
Нейросети в трейдинге: Адаптация прогноза при смене рыночного режима (OMPB)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования