Моделирование рынка: Position View (XVII)
Введение
Всем привет и добро пожаловать в очередную статью из серии, посвящённой созданию системы репликации/моделирования.
В предыдущей статье «Моделирование рынка: Position View (XVI)» мы настроили индикатор позиции так, чтобы он отображал финансовый результат открытой позиции. Однако не всем трейдерам нравится использовать этот режим отображения. Причины могут быть разными, хотя в некоторых случаях они кажутся мне вполне обоснованными и оправданными: чтобы жадность или страх не брали верх во время сделки. Глядя на денежные суммы, некоторые трейдеры боятся потерять ещё больше или отдать рынку уже полученную прибыль. В определённые моменты подобная ситуация может существенно навредить трейдеру. Поэтому некоторые трейдеры предпочитают отстраниться и не смотреть на открытую позицию, чтобы не поддаться искушению закрыть её раньше времени.
Хотя такой подход и может быть оправдан, он не вполне уместен, поскольку некоторые торговые символы могут входить в периоды высокой волатильности. В таких обстоятельствах ордера Stop Loss и Take Profit могут исполниться за пределами ожидаемых уровней, из-за чего сделка может дать неожиданный результат. Когда Take Profit оказывается больше ожидаемого, я не вижу, чтобы кто-то жаловался в социальных сетях или на других платформах для общения. Однако, когда убыток превышает запланированный, все начинают всех и всё ругать. В таких случаях ответственность лежит исключительно на трейдере, который должен был оставаться внимательным, поскольку держал открытую позицию в период высокой волатильности.
Тем не менее я здесь не для того, чтобы обсуждать этот вопрос. Я просто хотел объяснить, почему мы должны предлагать различные способы отображения данных позиции, поскольку трейдеру может быть неудобен тот или иной вариант.
Адаптировать код, чтобы обеспечить эту возможность, — задача далеко не самая сложная. Cамо решение довольно простое, но его всё равно интересно разобрать. Это может оказаться очень полезным для энтузиастов, которые используют эти статьи как вспомогательный материал для изучения и более глубокого освоения MQL5. Итак, перейдём сразу к делу.
Создание системы смены режима отображения
Хорошо. Прежде чем начать, давайте посмотрим на код класса 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. //+------------------------------------------------------------------+ 011. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 012. //+------------------------------------------------------------------+ 013. #define def_PathBtns "Images\\Market Replay\\Orders\\" 014. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 015. #resource "\\" + def_Btn_Close; 016. //+------------------------------------------------------------------+ 017. #include "..\Auxiliar\C_Mouse.mqh" 018. //+------------------------------------------------------------------+ 019. #ifdef def_FontName 020. "Why are you trying to do this?" 021. #else 022. #define def_FontName "Lucida Console" 023. #define def_FontSize 10 024. #endif 025. //+------------------------------------------------------------------+ 026. class C_ElementsTrade : private C_Mouse 027. { 028. private : 029. //+------------------------------------------------------------------+ 030. struct st00 031. { 032. struct st_01 033. { 034. short Width, 035. Height, 036. digits; 037. }Text; 038. ulong ticket; 039. string szPrefixName, 040. szDescr; 041. EnumEvents ev; 042. double price, 043. open, 044. volume, 045. var; 046. bool bClick, 047. bIsBuy; 048. char weight; 049. color _color; 050. int sizeText; 051. }m_Info; 052. //+------------------------------------------------------------------+ 053. short UpdateViewPort(const double price, short size = 0, uint ui = 0) 054. { 055. static short _SizeControls; 056. static short _Width; 057. uint x, y; 058. 059. if (size > 0) 060. { 061. size += (short)(ui + 8); 062. _SizeControls = (_SizeControls > size ? _SizeControls : size); 063. size = (short)(_SizeControls - ui - 12); 064. _Width = (_Width > size ? _Width : size); 065. }else 066. { 067. ChartTimePriceToXY(0, 0, 0, price, x, y); 068. x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2))); 069. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 070. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 071. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 072. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 073. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10); 074. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2)); 075. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + _Width + 20); 076. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 077. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20); 078. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 079. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10); 080. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2)); 081. } 082. return _Width; 083. } 084. //+------------------------------------------------------------------+ 085. inline void CreateLinePrice(void) 086. { 087. string szObj; 088. 089. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 090. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 091. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 092. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 093. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 094. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 095. macro_LineInFocus(false); 096. CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 097. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 098. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 099. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 100. } 101. //+------------------------------------------------------------------+ 102. inline void CreateBoxInfo(const bool bMove) 103. { 104. string szObj; 105. const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0}; 106. 107. CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 108. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 109. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 110. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon))); 111. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15)); 112. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 113. } 114. //+------------------------------------------------------------------+ 115. inline void CreateObjectInfoText(void) 116. { 117. string szObj; 118. 119. CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault)); 120. ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName); 121. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize); 122. ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack); 123. ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color); 124. ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER); 125. ObjectSetInteger(0, szObj, OBJPROP_READONLY, true); 126. } 127. //+------------------------------------------------------------------+ 128. inline void CreateButtonClose(void) 129. { 130. string szObj; 131. 132. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 133. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 134. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 135. } 136. //+------------------------------------------------------------------+ 137. inline void AdjustDinamic(const string szTxt) 138. { 139. uint w, h; 140. 141. TextSetFont(def_FontName, def_FontSize * -10); 142. TextGetSize(szTxt, w, h); 143. m_Info.Text.Height = (uchar) h + 4; 144. m_Info.Text.Width = (uchar) w + 4; 145. m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32); 146. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XSIZE, m_Info.Text.Width); 147. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YSIZE, m_Info.Text.Height); 148. ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? 8 : 0)); 149. ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5); 150. } 151. //+------------------------------------------------------------------+ 152. public : 153. //+------------------------------------------------------------------+ 154. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true) 155. :C_Mouse(0, "") 156. { 157. ZeroMemory(m_Info); 158. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 159. m_Info._color = _color; 160. m_Info.szDescr = szDescr; 161. m_Info.bIsBuy = IsBuy; 162. m_Info.Text.digits = digits; 163. } 164. //+------------------------------------------------------------------+ 165. ~C_ElementsTrade() 166. { 167. ObjectsDeleteAll(0, m_Info.szPrefixName); 168. } 169. //+------------------------------------------------------------------+ 170. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0) 171. { 172. m_Info.sizeText = 0; 173. ObjectsDeleteAll(0, m_Info.szPrefixName); 174. m_Info.volume = (vol > 0 ? vol : m_Info.volume); 175. m_Info.var = (var > 0 ? var : m_Info.var); 176. if (price > 0) 177. { 178. CreateLinePrice(); 179. CreateButtonClose(); 180. CreateObjectInfoText(); 181. } 182. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 183. m_Info.open = open; 184. UpdateViewPort(m_Info.price = (price > 0 ? price : open)); 185. if (m_Info.ev != evMsgClosePositionEA) 186. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 187. } 188. //+------------------------------------------------------------------+ 189. void ViewValue(const double profit) 190. { 191. string szTxt; 192. 193. szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume); 194. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt); 195. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral)); 196. if (StringLen(szTxt) != m_Info.sizeText) 197. { 198. AdjustDinamic(szTxt); 199. m_Info.sizeText = StringLen(szTxt); 200. } 201. } 202. //+------------------------------------------------------------------+ 203. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 204. { 205. string sz0; 206. long _lparam = lparam; 207. double _dparam = dparam; 208. 209. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 210. switch (id) 211. { 212. case (CHARTEVENT_KEYDOWN): 213. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 214. _lparam = (long) m_Info.ticket; 215. _dparam = 0; 216. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 217. case CHARTEVENT_CUSTOM + evMsgSetFocus: 218. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 219. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 220. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 221. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 222. m_Info.bClick = false; 223. case CHARTEVENT_CHART_CHANGE: 224. UpdateViewPort(m_Info.price); 225. if (m_Info.ev != evMsgClosePositionEA) 226. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 227. break; 228. case CHARTEVENT_OBJECT_CLICK: 229. sz0 = GetPositionsMouse().szObjNameClick; 230. if (m_Info.bClick) 231. { 232. if (sz0 == def_NameBtnMove) 233. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 234. if (sz0 == def_NameBtnClose) 235. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL)); 236. } 237. m_Info.bClick = false; 238. break; 239. case CHARTEVENT_MOUSE_MOVE: 240. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 241. if (m_Info.weight > 1) 242. { 243. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 244. if (m_Info.ev != evMsgClosePositionEA) 245. ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam); 246. if (m_Info.bClick) 247. { 248. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 249. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 250. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 251. } 252. } 253. break; 254. } 255. } 256. //+------------------------------------------------------------------+ 257. }; 258. //+------------------------------------------------------------------+ 259. #undef macro_LineInFocus 260. //+------------------------------------------------------------------+ 261. #undef def_Btn_Close 262. #undef def_PathBtns 263. #undef def_FontName 264. #undef def_FontSize 265. //+------------------------------------------------------------------+ 266. #undef def_NameBackGround 267. #undef def_NameObjLabel 268. #undef def_NameInfoDirect 269. #undef def_NameBtnMove 270. #undef def_NameBtnClose 271. #undef def_NameHLine 272. //+------------------------------------------------------------------+
C_ElementsTrade
Почему я показываю его целиком? Из-за сложности, связанной с компиляцией всей системы, я постепенно показывал все изменения, которым подвергался код. Таким образом, те, кто захочет изучить проект или даже изменить его, получат доступ к исходному коду. Это масштабный проект, состоящий из нескольких отдельных файлов и приложений, которые должны работать как единая система, поэтому многим может быть трудно понять, как функционирует вся система. По этой причине в приложённых к статье файлах я включаю только исполняемые файлы. Тем не менее те, кто следит за этой серией статей и хочет получить исходный код, смогут получить его полностью по материалам, которые я показываю от статьи к статье.
Теперь, когда это прояснили, начнем с небольшого изменения или, точнее, добавим в код ещё один элемент. Изменение показано в следующем фрагменте.
25. //+------------------------------------------------------------------+ 26. class C_ElementsTrade : private C_Mouse 27. { 28. private : 29. //+------------------------------------------------------------------+ 30. struct stInfos 31. { 32. struct st_01 33. { 34. short Width, 35. Height, 36. digits; 37. }Text; 38. ulong ticket; 39. string szPrefixName, 40. szDescr; 41. EnumEvents ev; 42. double price, 43. open, 44. volume, 45. var, 46. tickSize; 47. bool bClick, 48. bIsBuy; 49. char weight; 50. color _color; 51. int sizeText; 52. enum {eValue, eFinance, eTicks, ePercentage} ViewMode; 53. }m_Info; 54. //+------------------------------------------------------------------+
Фрагмент из C_ElementsTrade
Обратите внимание, что в строке 30 было изменено имя структуры. Самое любопытное находится в строке 51. Эта конструкция может показаться экстравагантной и даже нелепой, поскольку вы, вероятно, раньше не видели ничего подобного. Рассмотрим строку 52 более подробно. По мере чтения статьи код станет понятнее, и вы поймёте, почему он определён именно так. Во-первых, у нас есть анонимное перечисление, то есть перечисление без имени. Это может показаться странным, но, поскольку в конце мы объявляем переменную этого типа, мы сможем без проблем использовать значения, определённые в перечислении. Тем не менее, главный вопрос, вероятно, в другом: почему это перечисление находится внутри структуры и какие последствия это будет иметь для кода?
При объявлении перечисления внутри структуры область его видимости ограничивается этой структурой. Этот приём позволяет создавать перечисления, значения которых имеют похожие имена. Одна из самых больших трудностей в программировании заключается не в написании кода, а в подборе подходящих имён. У вас может возникнуть гораздо больше трудностей с тем, чтобы вспомнить, что обозначает переменная с именем value_001, чем с тем, чтобы понять, что означает другая переменная с именем Price_01. Обратите внимание, что имя переменной, объявленной в строке 52, выглядит вполне понятным. Однако, когда вы начнёте программировать и реализовывать всё это, вы увидите, что такое имя может оказаться уже недостаточно описательным. То же самое может произойти и со значениями перечисления. Поскольку перечисление находится внутри структуры, я могу придать ему смысл через его принадлежность к этой структуре. Таким образом, переменная, объявленная в строке 52, приобретает конкретный смысл в рамках этой структуры.
После этого нам нужно изменить одну деталь в основном коде. Чтобы показать, что именно будет изменено, рассмотрим приведённый ниже фрагмент.
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 (ObjectFind(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. //+------------------------------------------------------------------+ . . . 084. //+------------------------------------------------------------------+ 085. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 086. { 087. double volume; 088. 089. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 090. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 091. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 092. switch (id) 093. { 094. case CHARTEVENT_CUSTOM + evUpdate_Position: 095. if (lparam != m_Infos.ticket) break; 096. if (!PositionSelectByTicket(m_Infos.ticket)) 097. { 098. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 099. return; 100. }; 101. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, m_Infos.digits, m_Infos.tickSize, StringFormat("%I64u : Position opening price.", m_Infos.ticket), m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)); 102. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, m_Infos.digits, m_Infos.tickSize, StringFormat("%I64u : Take Profit price.", m_Infos.ticket), m_Infos.bIsBuy); 103. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, m_Infos.digits, m_Infos.tickSize, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), m_Infos.bIsBuy); 104. volume = PositionGetDouble(POSITION_VOLUME); 105. (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 106. (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var); 107. (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var); 108. ProfitNow(); 109. break; 110. } 111. ChartRedraw(); 112. }; 113. //+------------------------------------------------------------------+
Фрагмент из C_ElementsTrade
Обратите внимание, что в строке 26 мы добавили новую переменную, которой присваиваем значение в строке 47. Мы выполняем эту операцию здесь, чтобы избежать её ненужного выполнения внутри класса C_ElementsTrade. Затем это значение передаётся классу в строках 101–103. Это значение будет использоваться как вспомогательное при отображении количества тиков. Не путайте это с величиной в пунктах, поскольку это не одно и то же.
Как вы, наверное, уже догадались, нам также нужно изменить конструктор класса. Это изменение показано в следующем фрагменте.
155. //+------------------------------------------------------------------+ 156. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true) 157. :C_Mouse(0, "") 158. { 159. ZeroMemory(m_Info); 160. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 161. m_Info._color = _color; 162. m_Info.szDescr = szDescr; 163. m_Info.bIsBuy = IsBuy; 164. m_Info.Text.digits = digits; 165. m_Info.tickSize = ticksize; 166. } 167. //+------------------------------------------------------------------+
Фрагмент C_ElementsTrade
Обратите внимание, что изменения очень просты, поэтому я не буду выделять их здесь отдельно. Мы можем сразу перейти к процедуре, которая формирует отображаемые значения, и адаптировать её для применения соответствующего формата. Итак, давайте рассмотрим новый код процедуры ViewValue, приведённый ниже.
191. //+------------------------------------------------------------------+ 192. void ViewValue(const double profit) 193. { 194. string szTxt; 195. 196. switch (m_Info.ViewMode) 197. { 198. case stInfos::eValue: 199. szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f ", MathAbs(profit)); 200. break; 201. case stInfos::eFinance: 202. szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f ", (MathAbs(profit) / m_Info.var) * m_Info.volume); 203. break; 204. case stInfos::eTicks: 205. szTxt = StringFormat(" %d ", (uint)MathRound(MathAbs(profit) / m_Info.tickSize)); 206. break; 207. case stInfos::ePercentage: 208. szTxt = StringFormat(" %.2f%% ", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2)); 209. break; 210. } 211. ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt); 212. ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral)); 213. if (StringLen(szTxt) != m_Info.sizeText) 214. { 215. AdjustDinamic(szTxt); 216. m_Info.sizeText = StringLen(szTxt); 217. } 218. } 219. //+------------------------------------------------------------------+
Фрагмент C_ElementsTrade
Здесь мы имеем особенно элегантный код благодаря той простоте, с которой он позволяет получить желаемый результат. Обратите внимание, что в строке 194 мы начинаем выбор с помощью switch между режимами, объявленными в строке 52. По умолчанию при выполнении конструктора будет активирован режим, значение которого равно нулю. Если вы хотите использовать финансовый режим в качестве режима по умолчанию, поместите его первым элементом перечисления в строке 52.
Теперь обратите внимание, что в строках 198, 201, 204 и 207 проверяется, какой вариант case сработал, чтобы применить нужный формат к отображаемому значению. Обратите внимание, что недостаточно просто указать значение перечисления. Также нужно указать имя структуры, за которым следует двойное двоеточие (::). Этот символ является оператором разрешения области видимости и позволяет компилятору определить, к какому перечислению принадлежит данное значение. Если у вас есть несколько перечислений с элементами, имена которых похожи, различать их может быть затруднительно. Однако при использовании немного более удачной схемы найти нужное перечисление становится проще. Именно по этой причине я изменил название структуры.
Приведённый выше код полностью решает проблему отображения значений в разных форматах. Тем не менее остаётся один вопрос: как сделать так, чтобы трейдеру или пользователю было как можно проще менять отображаемую информацию? Мы не хотим излишне усложнять себе задачу программирования и тем более сбивать пользователя с толку, когда он будет пытаться прочитать отображаемую информацию.
Существует несколько способов сделать это. Некоторые из них очень просты, а другие — немного сложнее. Однако всегда следует помнить следующее: для пользователя самый простой вариант — щёлкнуть по какому-нибудь элементу. Для нас, программистов, наоборот, было бы проще, если бы пользователь изменял значение, заданное как входной параметр индикатора.
Входные параметры, о которых я говорю, показаны на следующем изображении.

Использование параметров, показанных на предыдущем изображении, было бы, безусловно, самым простым способом реализовать это решение. Однако для трейдера или пользователя это не слишком интуитивно и не слишком практично, поскольку для этого ему пришлось бы открыть настройки индикатора и изменить значение. Зато щелчок по какому-либо элементу индикатора или по объекту на графике — действие вполне интуитивное. Поэтому мы будем использовать этот подход, даже если он потребует от нас, программистов, немного больше работы.
Такой подход создаёт новые проблемы и требует принятия определённых решений. Одно из этих решений касается использования индикатора позиции на счетах типа HEDGING, поскольку в этом случае на графике может быть несколько индикаторов позиции. Ещё одно решение касается того, как всё должно работать. Чтобы вам было легче понять этот второй вопрос — первый будет решён в ходе реализации — давайте предположим, что индикатор находится в режиме «значение».
А теперь давайте представим, что пользователь хочет отображать take profit как финансовый результат. При клике по нему в финансовый режим перейдёт только тейк-профит или также и другие значения, то есть цена открытия и стоп-лосс? А что, если вы захотите отображать тейк-профит как финансовый результат, цену открытия — в процентах, а стоп-лосс — как разницу между ценами? Разрешим ли мы такую комбинацию? Обратите внимание, что все эти аспекты нужно определить до начала реализации решения. Другой вопрос — где пользователю или трейдеру нужно будет щёлкнуть, чтобы изменить формат значения. Обратите внимание, что, хотя написание кода может показаться простым, анализ этих моментов тоже является частью реализации. Если позже вы решите изменить это поведение, для этого может потребоваться больше или меньше усилий.
Итак, давайте рассмотрим моё предложение. Вы можете с ним не согласиться. Это ваше право. В любом случае, цель здесь — показать, как приходят к результату, который затем будет реализован. Я предлагаю, чтобы пользователь щёлкал по объекту OBJ_EDIT, чтобы менять формат. Формат будет циклически меняться. То есть каждый раз, когда пользователь щёлкает по OBJ_EDIT, будет выбираться следующий режим, заданный в перечислении.
Этим я только что дал вам подсказку о следующей процедуре, которую мы изменим. И да, это именно та процедура, которую вы, вероятно, и представили: процедура DispatchMessage класса C_ElementsTrade.
Но сначала мы внесём ещё одно изменение, показанное ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Значение 23. datetime _datetime; // 1 Значение 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Значения 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Значения 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Значений 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Событие «тик-так» 31. evHideMouse, //Скрыть линию цены курсора мыши 32. evShowMouse, //Показать линию цены под курсором мыши 33. evHideBarTime, //Скрыть время бара 34. evShowBarTime, //Показать время бара 35. evHideDailyVar, //Скрыть дневное изменение 36. evShowDailyVar, //Показать дневное изменение 37. evHidePriceVar, //Скрыть мгновенное изменение 38. evShowPriceVar, //Показать мгновенное изменение 39. evCtrlReplayInit, //Инициализировать управление воспроизведением 40. evChartTradeBuy, //событие рыночной покупки 41. evChartTradeSell, //событие рыночной продажи 42. evChartTradeCloseAll, //Событие для закрытия позиций 43. evChartTrade_At_EA, //событие для связи 44. evEA_At_ChartTrade, //событие для связи 45. evChatWriteSocket, //Событие для мини-чата 46. evChatReadSocket, //Событие для мини-чата 47. evUpdate_Position, //событие для связи 48. evMsgClosePositionEA, //событие для связи 49. evMsgCloseTakeProfit, //событие для связи 50. evMsgCloseStopLoss, //событие для связи 51. evMsgNewTakeProfit, //событие для связи 52. evMsgNewStopLoss, //событие для связи 53. evMsgSetFocus, //событие для связи 54. evMsgServiceSwapStatus, //Событие для остановки сервиса репликации/моделирования рынка 55. evMsgSwapViewModePosition //событие переключения режима 56. }; 57. //+------------------------------------------------------------------+ 58. enum EnumPriority { //Список приоритетов объектов 59. ePriorityNull = -1, 60. ePriorityDefault = 0 61. }; 62. //+------------------------------------------------------------------+
Defines.mqh
Обратите внимание, что в строке 55 мы определили новое событие. Теперь мы можем перейти к процедуре DispatchMessage класса C_ElementsTrade. Чтобы максимально ясно объяснить, как это работает, мы разберём это в отдельном разделе. Так будет проще разделить понятия и помочь вам понять, что происходит.
Процедура DispatchMessage. Какая путаница
Хотя название этой темы наводит на мысль, что дальше будет путаница, на самом деле все покажется вам довольно простым, если вы поняли предыдущие статьи, изучали материал и старались разобраться в том, что излагалось в каждой из них. Здесь нет ничего запутанного или сложного. Прежде чем продолжить, я хочу показать вам одну деталь. Посмотрите на следующее изображение.

Обратите внимание, что я выделил одну строку в выводе компилятора. Что в ней такого особенного, что она заслуживает нашего внимания? Эта строка появилась из-за предупреждения компилятора. Чтобы понять, откуда она взялась, давайте посмотрим на фрагмент кода, который ее вызвал.
219. //+------------------------------------------------------------------+ 220. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 221. { 222. string sz0; 223. long _lparam = lparam; 224. double _dparam = dparam; 225. 226. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 227. switch (id) 228. { 229. case (CHARTEVENT_KEYDOWN): 230. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 231. _lparam = (long) m_Info.ticket; 232. _dparam = 0; 233. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 234. case CHARTEVENT_CUSTOM + evMsgSetFocus: 235. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 236. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 237. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 238. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 239. m_Info.bClick = false; 240. case CHARTEVENT_CHART_CHANGE: 241. UpdateViewPort(m_Info.price); 242. if (m_Info.ev != evMsgClosePositionEA) 243. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 244. break; 245. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 246. if (m_Info.ticket == (ulong)(_lparam)) 247. { 248. m_Info.ViewMode = (stInfos::e1)((m_Info.ViewMode + 1) & 0x03); 249. EventChartCustom(0, evUpdate_Position, _lparam, 0, NULL); 250. } 251. break; 252. case CHARTEVENT_OBJECT_CLICK: 253. sz0 = GetPositionsMouse().szObjNameClick; 254. if (m_Info.bClick) 255. { 256. if (sz0 == def_NameBtnMove) 257. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 258. if (sz0 == def_NameBtnClose) 259. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL)); 260. if (sz0 == def_NameObjLabel) 261. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, 0, NULL); 262. } 263. m_Info.bClick = false; 264. break; 265. case CHARTEVENT_MOUSE_MOVE: 266. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 267. if (m_Info.weight > 1) 268. { 269. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 270. if (m_Info.ev != evMsgClosePositionEA) 271. ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam); 272. if (m_Info.bClick) 273. { 274. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 275. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 276. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 277. } 278. } 279. break; 280. } 281. } 282. //+------------------------------------------------------------------+
Фрагмент C_ElementsTrade
Обратите внимание именно на строку, указанную на предыдущем изображении. В ней мы выполняем преобразование типа. Код фрагмента уже обновлен, чтобы больше не вызывать это предупреждение. Тем не менее я хочу, чтобы вы внимательно изучили строку 248. Если вы внимательно следили за кодом, то заметите, что там я использую нечто, чего в коде нет. Поэтому нам нужно исправить одну деталь, объясненную в предыдущем разделе.
Суть в следующем: хотя перечисления могут быть анонимными, как мы сделали в строке 52 класса C_ElementsTrade, мы не сможем корректно выполнять такое преобразование без имени типа перечисления. Это не связано ни с ошибкой в коде, ни с ошибкой компилятора. В MQL5 существует специальная функция под названием EnumToString, предназначенная для преобразования числового значения перечисления в значение типа string. Речь идет не о какой угодно строке, а о точном имени, связанном с этим значением внутри перечисления.
Но подождите минуту. На данном этапе мы не преобразуем значение в строку и не пытаемся получить имя, связанное с этим числовым значением внутри перечисления. Тогда почему мы должны обратить внимание на предупреждение компилятора? Разве мы не можем просто проигнорировать его? Предупреждение указывает на то, что преобразование значения перечисления в строку может завершиться неудачей, если попытаться его выполнить. Не стоит игнорировать все предупреждения. Некоторые из них можно игнорировать, а другие нужно исправить. Каждая ситуация требует отдельного анализа; общего правила, применимого ко всем случаям, не существует.
В этом случае преобразование типа будет выполнено правильно. Однако, поскольку я не люблю видеть предупреждения, предпочитаю устранять причину, которая их вызывает. Для этого нам нужно всего лишь заменить строку 52, приведённую в предыдущем разделе, на строку ниже.
052. enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;
После этого, если оставить код процедуры DispatchMessage таким, как в предыдущем фрагменте, компилятор перестанет выдавать предупреждение. Теперь мы можем вернуться к процедуре DispatchMessage класса C_ElementsTrade и понять, как она работает. Само по себе это несложно, хотя может показаться запутанным, если вам не удаётся следить за этой последовательностью статей. Но если вам удастся разобраться в объяснениях из статей этой серии о системе репликации/моделирования, то этот код на самом деле очень прост. Итак, давайте рассмотрим, что нового он в себя включает.
В строке 260 мы проверяем, был ли нажат объект OBJ_EDIT, который содержит значение, отображаемое трейдеру или пользователю платформы. Если условие выполняется, в строке 261 мы отправляем пользовательское событие. Я уже объяснял, как интерпретировать этот процесс. Затем, в строке 245, мы перехватываем это пользовательское событие.
В строке 246 мы проверяем, совпадает ли тикет с тем, который использует индикатор позиции. Если это так, то в строке 248 мы циклически переключаемся между четырьмя типами отображения. После смены режима в строке 249 мы генерируем новое событие, цель которого — обновить остальные сегменты индикатора позиции. То есть, даже если вы нажмёте на тейк-профит, стоп-лосс и результат сделки тоже обновятся. Таким образом, мы избегаем ситуации, когда в одном сегменте отображается процент, а в остальных — значения другого типа. Результат можно увидеть в следующей анимации.

Обратите внимание, что, хотя мы добавили в процедуру DispatchMessage совсем немного кода, нам удалось добиться именно того поведения, которое и предполагалось, и в итоге получить удовлетворительный результат. Тем не менее, я всё же хочу объяснить ситуацию, которая может относиться и к вашему случаю. Предположим, что вы хотите использовать этот индикатор позиции на счёте типа HEDGING. Для тех, кто не знает, этот тип счёта позволяет одновременно удерживать позицию на покупку и позицию на продажу по одному и тому же символу. Даже если у вас уже есть открытая позиция на покупку, вы можете открыть ещё одну, и при этом одна позиция не будет напрямую влиять на другую. Эта независимость относительна, поскольку с финансовой точки зрения обе позиции будут в той или иной степени взаимосвязаны.
Что произойдёт, если на графике будет два индикатора позиции? По сути, они будут независимы друг от друга. При изменении способа отображения данных позиции будут изменяться только относящиеся к ней сегменты: цена открытия, тейк-профит и стоп-лосс. У остальных позиций способ отображения результата останется без изменений. Это связано с проверкой, выполняемой в строке 246.
Если вы хотите, чтобы изменение способа отображения данных применялось и к остальным позициям, достаточно удалить проверку в строке 246. Однако прежде чем это сделать, следует учесть, что существует проблема, а точнее — отсутствие связи между индикаторами позиции.
Чтобы это понять, сначала нужно знать, что индикатор всегда запускается в нулевом режиме перечисления, объявленного в строке 52. Поэтому при добавлении на график он начинает работу в режиме НОЛЬ. Затем вы используете механизм, показанный в предыдущей анимации, и переключаете режим отображения, например, на режим ОДИН. Помните, что у нас есть ЧЕТЫРЕ РЕЖИМА. Затем вы открываете новую позицию на счёте типа HEDGING. В этот момент один индикатор будет находиться в режиме ОДИН, а другой — в режиме НОЛЬ. А теперь подумайте вот о чём: если вы удалите проверку в строке 246, то при переключении режима отображения индикатора новой позиции в режим «ОДИН» вы также измените отображение первой позиции: оно переключится из режима «ОДИН» в режим «ТРИ». Вот тут и начинается путаница. Если вы попытаетесь вернуть режим отображения первой позиции в режим «ОДИН», то также измените режим новой позиции — на этот раз на режим «ТРИ».
Иными словами, не стоит изменять работу системы, ограничиваясь лишь удалением проверки или любого другого элемента. Прежде чем это делать, следует остановиться и проанализировать последствия. Я не хочу отговаривать вас от изменения или адаптации кода под ваши конкретные нужды. Я лишь хочу, чтобы вы понимали, что делаете, прежде чем пытаться это сделать.
Но я не хочу оставлять вас наедине с этой проблемой. Давайте рассмотрим возможное решение. При добавлении индикатора или советника на график сначала выполняется код функции OnInit. MetaTrader 5 также генерирует событие CHARTEVENT_CHART_CHANGE, которое обрабатывается функцией OnChartEvent. Эта информация важна, поскольку позволяет нам синхронизировать индикаторы позиции сразу после добавления каждого из них на график.
Пока я не буду усложнять ситуацию. Я покажу вам лишь часть решения. Позже я покажу, как создать ещё лучшее решение, поскольку в конечном итоге оно понадобится. Давайте посмотрим, как можно синхронизировать индикаторы с минимальными усилиями.
Первая часть решения приведена в следующем фрагменте.
219. //+------------------------------------------------------------------+ 220. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 221. { 222. string sz0; 223. long _lparam = lparam; 224. double _dparam = dparam; 225. 226. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 227. switch (id) 228. { 229. case (CHARTEVENT_KEYDOWN): 230. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 231. _lparam = (long) m_Info.ticket; 232. _dparam = 0; 233. EventChartCustom(0, evUpdate_Position, _lparam, 0, ""); 234. case CHARTEVENT_CUSTOM + evMsgSetFocus: 235. if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)) 236. UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price); 237. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 238. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 239. m_Info.bClick = false; 240. case CHARTEVENT_CHART_CHANGE: 241. UpdateViewPort(m_Info.price); 242. if (m_Info.ev != evMsgClosePositionEA) 243. ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price); 244. break; 245. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 246. m_Info.ViewMode = (stInfos::e1)_dparam; 247. m_Info.ViewMode = (stInfos::e1)((m_Info.ViewMode + 1) & 0x03); 248. EventChartCustom(0, evUpdate_Position, _lparam, 0, NULL); 249. break; 250. case CHARTEVENT_OBJECT_CLICK: 251. sz0 = GetPositionsMouse().szObjNameClick; 252. if (m_Info.bClick) 253. { 254. if (sz0 == def_NameBtnMove) 255. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, ""); 256. if (sz0 == def_NameBtnClose) 257. EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL)); 258. if (sz0 == def_NameObjLabel) 259. EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL); 260. } 261. m_Info.bClick = false; 262. break; 263. case CHARTEVENT_MOUSE_MOVE: 264. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 265. if (m_Info.weight > 1) 266. { 267. UpdateViewPort(_dparam = GetPositionsMouse().Position.Price); 268. if (m_Info.ev != evMsgClosePositionEA) 269. ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam); 270. if (m_Info.bClick) 271. { 272. if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss)) 273. EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 274. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 275. } 276. } 277. break; 278. } 279. } 280. //+------------------------------------------------------------------+
Фрагмент C_ElementTrade
Обратите внимание, что теперь в строке 259 мы указываем текущее значение отображения с помощью параметра dparam. Когда выполнится код в строке 246, мы присвоим всем индикаторам позиции значение, полученное через dparam. Именно в этой точке происходит синхронизация. Я мог бы объединить строки 246 и 247 в одну, но предпочел оставить их раздельными, чтобы наглядно показать вам, как выполняется синхронизация. Если вы запустите код и измените отображение, то получите результат, показанный в следующей анимации.

Вопрос: почему сегменты не отображаются синхронно? Ответ: они синхронизированы, но нам нужно обновить график, чтобы синхронизация отобразилась визуально. Чем это вызвано? Причина в строке 248, которая обновляет только индикатор, получивший событие клика пользователя. Как мы можем решить эту проблему? Все просто: достаточно обновить все позиции. Для этого мы изменим код, как показано ниже.
245. case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition: 246. m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03); 247. for (int c0 = PositionsTotal() - 1; c0 >= 0; c0--) 248. EventChartCustom(0, evUpdate_Position, PositionGetTicket(c0), 0, NULL); 249. break; 250. case CHARTEVENT_OBJECT_CLICK:
Фрагмент C_ElementsTrade
Обратите внимание, что теперь я сосредоточусь исключительно на той части, которая действительно изменилась. Две строки из предыдущего кода были объединены в одну. То есть строки 246 и 247 из предыдущего фрагмента теперь объединены в строке 246, поскольку, полагаю, вы уже поняли, как это работает. Собственно, исправление заключается в добавлении новой строки 247. Обратите внимание, что, в отличие от предыдущего кода, теперь событие evUpdate_Position запускается для всех открытых позиций. Тем самым мы решаем проблему. Результат можно увидеть в следующей анимации.

Как видите, это довольно просто. Тем не менее, это решение все еще не идеально. Позже я покажу более удачный код с гораздо более интересным принципом работы. Пока этого подхода достаточно.
Заключительные замечания
В этой статье я показал, как вы, энтузиаст и жаждущий знаний, можете сделать индикатор позиции ещё более интересным в использовании. Тем не менее, он ещё не полностью готов и требует ещё нескольких доработок, прежде чем мы сможем действительно его использовать. Цель состоит в том, чтобы получить индикатор, который будет работать как в системе репликации/моделирования, так и на рынке — как на демо-счёте, так и на реальном счёте. Цель этой серии — представить весь процесс в максимально наглядной форме. Надеюсь, вы сможете следить за ходом разработки, даже если в какие-то моменты она будет казаться медленной и будет складываться впечатление, что у неё мало шансов действительно начать работать в системе репликации/моделирования.
Но мы движемся вперёд. Шаг за шагом. Для тех, кто хочет просто опробовать приложения, разработанные к настоящему моменту, я включу в приложенные файлы уже скомпилированные версии. Вам нужно будет лишь распаковать файлы и использовать их, чтобы проверить текущее состояние проекта. Помните, что эти приложения предназначены для учебных целей и могут содержать ошибки. Поэтому используйте их на свой страх и риск.
| Файл | Описание |
|---|---|
| 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/13447
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Осваиваем графики Kagi в MQL5 (Часть 2): Реализация автоматизированной торговли на основе Kagi
Автоматизированный риск-менеджмент для прохождения челленджей проп-фирм
От начального до среднего уровня: Очереди, списки и деревья (VIII)
От начального до среднего уровня: Очереди, списки и деревья (VII)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования