Моделирование рынка: Position View (X)
Введение
Приветствую всех и добро пожаловать в новую статью из серии о том, как создать систему репликации/моделирования.
В предыдущей статье Моделирование рынка: Position View (IX) мы начали изучать, как должно быть реализовано перемещение линий тейк-профита и стоп-лосса. Поскольку я хочу объяснить всем и сделать предельно понятным то, как и почему происходят изменения в коде, в этой статье мы попытаемся решить проблему, представленную в предыдущей статье. Проблема заключается в ZOrder. Если вы не знаете, какое значение имеет свойство ZOrder для объектов, не волнуйтесь, просто изучите предыдущие статьи этой серии, так как там я объясняю важность правильного определения значения этого свойства.
Кроме того, возникает вопрос: как мы можем использовать ZOrder в своих интересах? Ответ прост: мы не можем. Это кажется абсурдным и совершенно бессмысленным. Тем не менее, как бы хорошо вы ни умели программировать, уважаемый читатель, вам не удастся превзойти то, что уже запрограммировано в MetaTrader 5 для управления ZOrder.
Тем не менее, нам необходимы средства для обработки создаваемых графических объектов. Предложение, представленное в предыдущей статье, отлично подходит для некоторых сценариев. В данном случае нам потребуется нечто более сложное, учитывая специфику рассматриваемой проблемы. Поэтому мы не будем пытаться заменить существующие в MetaTrader 5 механизмы управления ZOrder и, конечно же, проверять, какой объект находится на переднем плане или скрыт другим объектом. Мы сделаем нечто совершенно другое. Здесь я покажу, какие изменения необходимо внести в код, чтобы задействовать часть того, что MetaTrader 5 уже делает за нас. Иными словами, указывать, с каким объектом следует взаимодействовать, а с каким — нет, после получения клика.
Чтобы вы могли понять, что мы будем делать, сначала необходимо рассмотреть довольно простой код, который, тем не менее, крайне полезен для понимания того, как подойти к решению нашей задачи.
Простейший код
Чтобы разобраться в этом, необходимо использовать то, что вы действительно сможете понять. Многие обычно думают, что для решения проблемы нам нужно создать замысловатое или крайне сложное решение. Но на практике нам не следует так поступать. На практике нам нужно создать что-то максимально простое. Мы должны использовать приложение и понимать, о чем оно нам сообщает. Для демонстрации этого мы будем использовать код, приведенный ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property indicator_chart_window 04. #property indicator_plots 0 05. //+------------------------------------------------------------------+ 06. #define debug(A) Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. string sz1 = "Object #01"; 11. 12. ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0); 13. ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 100); 14. ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 100); 15. 16. sz1 = "Object #02"; 17. ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0); 18. ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 200); 19. ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 200); 20. 21. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 22. 23. return INIT_SUCCEEDED; 24. } 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, 27. const int prev_calculated, 28. const int begin, 29. const double &price[]) 30. { 31. return rates_total; 32. } 33. //+------------------------------------------------------------------+ 34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 35. { 36. switch(id) 37. { 38. case CHARTEVENT_OBJECT_CLICK: 39. debug(sparam); 40. break; 41. case CHARTEVENT_MOUSE_MOVE: 42. if (sparam != "0") 43. debug(sparam); 44. break; 45. } 46. }; 47. //+------------------------------------------------------------------+ 48. void OnDeinit(const int reason) 49. { 50. ObjectsDeleteAll(0, "Object", -1, OBJ_BUTTON); 51. } 52. //+------------------------------------------------------------------+
Индикатор для тестирования
Данный код предельно прост. Всё, что он делает, - создает два объекта со стандартным ZOrder, то есть равным нулю, и выводит сообщение при возникновении события. Всё просто. Это может показаться глупым и даже наивным, но оно способно показать нам нечто чрезвычайно важное и полезное.
Когда мы запустим этот код на графике, мы получим результат, который можно увидеть в следующей анимации:

В этой анимации главным объектом внимания являются появляющиеся сообщения. Забудьте обо всем остальном и сосредоточьтесь на сообщениях. Обратите внимание, что здесь прослеживается закономерность. И что произойдет теперь, если мы добавим объект, который будет перекрывать кнопки? Что случится? Чтобы ответить на этот вопрос, просто посмотрите на следующую анимацию:

Ещё раз, обратите внимание на отображаемые сообщения. Теперь сравните их и скажите: в чем здесь закономерность? Если вы не заметили, попробуйте посмотреть на сообщения и код. И подумайте: какая здесь закономерность?
Если вам это не понятно, схема следующая: когда мы щелкаем по объекту на графике, MetaTrader 5 сначала запускает событие CHARTEVENT_MOUSE_MOVE, а затем событие CHARTEVENT_OBJECT_CLICK. Знать это важно. Почему? Зная это, мы можем использовать ту же самую закономерность в своих интересах для решения задачи, когда на графике много объектов. Таким образом, вопреки ожиданиям многих, мы не будем пытаться заменить существующий механизм в MetaTrader 5. Мы воспользуемся этим в своих интересах. Для этого данный шаблон, который вы можете протестировать на своей платформе, будет управляться нашими приложениями. Таким образом, когда мы будем проводить проверку с индикатором мыши, мы сможем игнорировать клики по объектам, которые имеют события в наших приложениях.
Возможно, вы подумаете: "Но как мы это сделаем? Мы уже довольно далеко продвинулись в разработке кода. Неужели нам придётся вернуться к истокам и полностью отказаться от всего, что уже было создано?" Нет, уважаемый читатель. Как я уже говорил, цель этой серии статей заключается не в том, чтобы показать, как непосредственно разработать систему репликации/моделирования, а в том, чтобы продемонстрировать, как программист должен подходить к возникающим проблемам. Многие считают, что создание приложения — это простой и беспроблемный процесс. Но те, кто действительно погружается в этот мир, знают, что всё устроено довольно извилисто. Зачастую нам приходится решать проблемы разной степени сложности.
Данная проблема с ZOrder во многих случаях может быть довольно сложной задачей. Вы, уважаемый читатель, могли бы просто подумать, что ни в один из моментов разработки у меня, как у программиста, не возникало проблем или трудностей. Это связано с тем, что если бы я просто исправил код и опубликовал его уже без ошибок, у вас могло бы возникнуть иллюзорное ощущение, будто все прошло идеально, и что если у вас, уважаемый читатель, возникают трудности с написанием кода, то это лишь потому, что вы, простите, полный профан. Но это не так. Все мы сталкиваемся с трудностями. То, как мы преодолеваем эти трудности, позволяет нам учиться и становиться профессионалом более или менее высокого уровня.
Итак, давайте подумаем над следующим, и я хочу, чтобы вы попытались проследить за ходом рассуждений. Как мы можем использовать в свою пользу эту информацию, полученную от этой простой программки, чтобы нам пришлось как можно меньше модифицировать уже спроектированный и реализованный код? Это действительно тот вопрос, который заставил бы многих в итоге отказаться от попыток его решения, поскольку это кажется крайне сложной задачей. Но, как я уже сказал, я хочу показать вам, уважаемый читатель, что если вы остановитесь на несколько мгновений и подумаете, то в итоге поймете, что необходимо сделать.
Обновление кода
Прежде чем продолжить, я надеюсь, вы действительно попытались придумать решение проблемы. Потому что, как бы невероятно это ни звучало, на самом деле всё проще, чем многие могут себе представить.
Первым делом мы удалим уровни ZOrder. На самом деле, мы не будем их полностью исключать, а скорее мы их переопределим так, чтобы максимально использовать встроенную поддержку MetaTrader 5. Таким образом, MetaTrader 5 сможет легко сообщить нам, какой объект действительно получил событие клика. Таким образом мы изменим файл, который можно увидеть ниже.
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 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position, //Event to communication 48. evMsgClosePositionEA, //Event to communication 49. evMsgCloseTakeProfit, //Event to communication 50. evMsgCloseStopLoss, //Event to communication 51. evMsgNewTakeProfit, //Event to communication 52. evMsgNewStopLoss //Event to communication 53. }; 54. //+------------------------------------------------------------------+ 55. enum EnumPriority { //Priority list on objects 56. ePriorityNull = -1, 57. ePriorityDefault = 0 58. }; 59. //+------------------------------------------------------------------+
Defines.mqh
Обратите внимание, что теперь в EnumPriority меньше типов перечислений. Таким образом, мы получаем стандартный тип или по умолчанию тип, равный нулю. Также у нас есть ещё один — ePriorityNull. Данный тип следует применять ко всем объектам, которые не должны подвергаться никакому взаимодействию. Что ж, после внесения этого изменения нам придется заново скомпилировать весь наш код. Это помогает обеспечить его полную стабильность. При попытке компиляции индикатора Chart Trade возникают ошибки, связанные с классом C_ChartFloatingRAD. Их можно увидеть на следующем изображении:

На этом этапе многие уже начинают рвать на себе волосы, думая: что же мы наделали? Так больше ничего не получится. Но так ли это будет на самом деле? Перейдя к месту, где сообщается о первой ошибке, то есть к строке 169 класса C_ChartFloatingRAD, мы увидим её причину. Чтобы исправить это, просто замените исходный код тем кодом, который показан в следующем фрагменте:
164. //+------------------------------------------------------------------+ 165. template <typename T > 166. void CreateObjectEditable(eObjectsIDE arg, T value) 167. { 168. DeleteObjectEdit(); 169. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, ePriorityDefault); 170. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 171. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 172. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 173. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 174. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 175. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 176. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 177. ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 178. ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 179. ChartRedraw(); 180. } 181. //+------------------------------------------------------------------+
Фрагмент файла C_ChartFloatingRAD.mqh
После внесения изменений мы попытались заново скомпилировать индикатор Chart Trade. И его результат показан ниже:

Иными словами, успех. Мы можем переходить к следующему. Что касается индикатора мыши, здесь никаких изменений не будет, поскольку его ZOrder не претерпел никаких изменений. Таким образом, следующим шагом станет компиляция индикатора позиции. Но поскольку именно в коде класса C_ElementsTrade возникнут проблемы с ZOrder и этот же код претерпит дополнительные изменения, я пока не буду показывать модифицированный код. Перейдем к этапу использования той информации, которую мы получили в предыдущем разделе. То есть мы собираемся реализовать поддержку, которую нам предоставит MetaTrader 5. Таким образом, при клике на что-либо мы сможем точно узнать, по какому именно объекту был совершен клик. Однако при проведении исследования клик следует игнорировать.
Для этого потребуются глубокие и радикальные перемены. Даже просто представить это крайне сложно. То, что мог придумать и осуществить только великий гуру. Поэтому обратите внимание, насколько сложным и запутанным является решение. Его можно увидеть в следующем коде:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. public : 011. //+------------------------------------------------------------------+ 012. struct st_Mouse 013. { 014. struct st00 015. { 016. short X_Adjusted, 017. Y_Adjusted, 018. X_Graphics, 019. Y_Graphics; 020. double Price; 021. datetime dt; 022. }Position; 023. uchar ButtonStatus; 024. bool ExecStudy; 025. string szObjNameClick; 026. }; 027. //+------------------------------------------------------------------+ 028. protected: 029. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 030. //+------------------------------------------------------------------+ 031. struct st_Terminal 032. { 033. ENUM_SYMBOL_CHART_MODE ChartMode; 034. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 035. long ID; 036. string szSymbol; 037. int Width, 038. Height, 039. nDigits, 040. SubWin, 041. HeightBar; 042. double PointPerTick, 043. ValuePerPoint, 044. VolumeMinimal, 045. AdjustToTrade; 046. }; 047. //+------------------------------------------------------------------+ 048. void CurrentSymbol(bool bUsingFull) 049. { 050. MqlDateTime mdt1; 051. string sz0, sz1; 052. datetime dt = macroGetDate(TimeCurrent(mdt1)); 053. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 054. 055. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 056. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 057. switch (eTS) 058. { 059. case DOL : 060. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 061. case IND : 062. case WIN : sz1 = "GJMQVZ"; break; 063. default : return; 064. } 065. sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS))); 066. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 067. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 068. } 069. //+------------------------------------------------------------------+ 070. inline void DecodeMousePosition(int xi, int yi) 071. { 072. int w = 0; 073. 074. xi = (xi > 0 ? xi : 0); 075. yi = (yi > 0 ? yi : 0); 076. ChartXYToTimePrice(m_Infos.ID, m_Mouse.Position.X_Graphics = (short)xi, m_Mouse.Position.Y_Graphics = (short)yi, w, m_Mouse.Position.dt, m_Mouse.Position.Price); 077. m_Mouse.Position.dt = AdjustTime(m_Mouse.Position.dt); 078. m_Mouse.Position.Price = AdjustPrice(m_Mouse.Position.Price); 079. ChartTimePriceToXY(m_Infos.ID, w, m_Mouse.Position.dt, m_Mouse.Position.Price, xi, yi); 080. yi -= (int)ChartGetInteger(m_Infos.ID, CHART_WINDOW_YDISTANCE, m_Infos.SubWin); 081. m_Mouse.Position.X_Adjusted = (short) xi; 082. m_Mouse.Position.Y_Adjusted = (short) yi; 083. } 084. //+------------------------------------------------------------------+ 085. private : 086. st_Terminal m_Infos; 087. st_Mouse m_Mouse; 088. struct mem 089. { 090. long Show_Descr, 091. Show_Date; 092. bool AccountLock; 093. }m_Mem; 094. //+------------------------------------------------------------------+ 095. inline void ChartChange(void) 096. { 097. int x, y, t; 098. 099. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 100. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 101. ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t); 102. ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y); 103. m_Infos.HeightBar = (int)(t - y) / 100; 104. } 105. //+------------------------------------------------------------------+ 106. public : 107. //+------------------------------------------------------------------+ 108. C_Terminal(const long id = 0, const uchar sub = 0, const bool bFull = false) 109. { 110. m_Infos.ID = (id == 0 ? ChartID() : id); 111. m_Mem.AccountLock = false; 112. m_Infos.SubWin = (int) sub; 113. CurrentSymbol(bFull); 114. ZeroMemory(m_Mouse); 115. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 116. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 117. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 118. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 119. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 120. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 121. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 122. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 123. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 124. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 125. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 126. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 127. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 128. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 129. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 130. ChartChange(); 131. } 132. //+------------------------------------------------------------------+ 133. ~C_Terminal() 134. { 135. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 136. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 137. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 138. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 139. } 140. //+------------------------------------------------------------------+ 141. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 142. { 143. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 144. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 145. } 146. //+------------------------------------------------------------------+ 147. inline const st_Terminal GetInfoTerminal(void) const 148. { 149. return m_Infos; 150. } 151. //+------------------------------------------------------------------+ 152. inline const st_Mouse GetPositionsMouse(void) const 153. { 154. return m_Mouse; 155. } 156. //+------------------------------------------------------------------+ 157. const double AdjustPrice(const double arg) const 158. { 159. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 160. } 161. //+------------------------------------------------------------------+ 162. inline datetime AdjustTime(const datetime arg) 163. { 164. int nSeconds= PeriodSeconds(); 165. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 166. 167. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 168. } 169. //+------------------------------------------------------------------+ 170. inline double FinanceToPoints(const double Finance, const uint Leverage) 171. { 172. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 173. 174. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 175. }; 176. //+------------------------------------------------------------------+ 177. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 178. { 179. static string st_str = ""; 180. 181. switch (id) 182. { 183. case CHARTEVENT_CHART_CHANGE: 184. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 185. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 186. ChartChange(); 187. break; 188. case CHARTEVENT_MOUSE_MOVE: 189. DecodeMousePosition((int)lparam, (int)dparam); 190. break; 191. case CHARTEVENT_OBJECT_CLICK: 192. m_Mouse.szObjNameClick = sparam; 193. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 194. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 195. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 196. break; 197. case CHARTEVENT_OBJECT_CREATE: 198. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 199. st_str = sparam; 200. break; 201. } 202. } 203. //+------------------------------------------------------------------+ 204. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const EnumPriority zOrder = ePriorityNull) const 205. { 206. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 207. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 208. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 209. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 210. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 211. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 212. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 213. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 214. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 215. } 216. //+------------------------------------------------------------------+ 217. bool IndicatorCheckPass(const string szShortName) 218. { 219. string szTmp = szShortName + "_TMP"; 220. 221. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 222. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szShortName)) < 0 ? 0 : m_Infos.SubWin); 223. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 224. { 225. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 226. Print("Only one instance is allowed..."); 227. SetUserError(C_Terminal::ERR_NoMoreInstance); 228. 229. return false; 230. } 231. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 232. 233. return true; 234. } 235. //+------------------------------------------------------------------+ 236. };
C_Terminal.mqh
Решение реализовано в приведенном выше коде. Вы заметили где? Если вы не будете следовать этой серии или изучать предоставленные мной материалы, вы, безусловно, не сможете найти решение. На самом деле, я намеренно разместил полный код класса C_Terminal, чтобы вы совершенно запутались и не поняли, где именно было реализовано решение.
Шутки в сторону. На самом деле, решение заключается в том, чтобы использовать MetaTrader 5 в своих интересах. Но для этого нам придётся прибегнуть к некой уловке. Этот приём можно реализовать здесь, в классе C_Terminal, или в том месте, где будет фактически использоваться реализованный здесь код. Возможно, вы подумаете: "разве реализация не должна происходить в классе C_Mouse, раз он отвечает за передачу событий клика в наши приложения?" Да, действительно, но если мы попытаемся это сделать, у нас возникнет небольшая проблема. Посмотрите ещё раз на анимацию. Обратите внимание, что событие CHARTEVENT_MOUSE_MOVE предшествует событию CHARTEVENT_OBJECT_CLICK. И когда мы анализируем, был ли клик по объекту во время анализа, у нас будет некоторая потеря синхронизации событий или, лучше сказать, когда вы кликнете по объекту, класс C_Mouse всё ещё будет указывать на объект, который получил предыдущий клик. Это может показаться странным. Но если мы посмотрим на класс C_ElementsTrade, всё станет яснее.
Итак, решение снова заключалось в добавлении новой переменной. Она находится в строке 25 и используется только в строке 192. Как уже говорилось, мы могли бы поместить решение в тот же код, который будет его использовать. Но поскольку я хочу, чтобы всё было очень хорошо организовано, мы разместим это здесь. То есть, в классе C_Terminal. Таким образом, теперь мы можем увидеть код индикатора позиции.
Обновление индикатора позиции
После внесения изменений в остальные приведенные выше коды, мы можем увидеть, что нужно было изменить в индикаторе позиции. Для начала было принято решение, что оператор или пользователь больше не смогут изменять цвета, используемые в линиях. Они будут стандартизированы внутри индикатора. Таким образом, код в основном файле изменился, и его можно увидеть ниже.
//+------------------------------------------------------------------+ 01. #property copyright "Daniel Jose" 02. #property icon "/Images/Market Replay/Icons/Positions.ico" 03. #property description "Indicator for tracking an open position on the server." 04. #property description "This should preferably be used together with an Expert Advisor." 05. #property description "For more details see the same article." 06. #property version "1.122" 07. #property link "https://www.mql5.com/pt/articles/13274" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #define def_ShortName "Position View" 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 14. #include <Market Replay\Defines.mqh> 15. //+------------------------------------------------------------------+ 16. input ulong user00 = 0; //For Expert Advisor use 17. //+------------------------------------------------------------------+ 18. C_IndicatorPosition *Positions = NULL; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 23. Positions = new C_IndicatorPosition(); 24. if (!Positions.CheckCatch(user00)) 25. { 26. ChartIndicatorDelete(0, 0, def_ShortName); 27. return INIT_FAILED; 28. } 29. 30. return INIT_SUCCEEDED; 31. } 32. //+------------------------------------------------------------------+ 33. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 34. { 35. return rates_total; 36. } 37. //+------------------------------------------------------------------+ 38. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 39. { 40. (*Positions).DispatchMessage(id, lparam, dparam, sparam); 41. }; 42. //+------------------------------------------------------------------+ 43. void OnDeinit(const int reason) 44. { 45. delete Positions; 46. } 47. //+------------------------------------------------------------------+
Индикатор позиции:
В связи с этим код класса C_IndicatorPosition немного изменился. Полный текст нового кода можно увидеть ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_ElementsTrade.mqh" 05. //+------------------------------------------------------------------+ 06. class C_IndicatorPosition 07. { 08. private : 09. struct st00 10. { 11. ulong ticket; 12. string szShortName; 13. }m_Infos; 14. C_ElementsTrade *Open, *Stop, *Take; 15. //+------------------------------------------------------------------+ 16. public : 17. //+------------------------------------------------------------------+ 18. C_IndicatorPosition() 19. { 20. ZeroMemory(m_Infos); 21. Open = Take = Stop = NULL; 22. } 23. //+------------------------------------------------------------------+ 24. ~C_IndicatorPosition() 25. { 26. delete Open; 27. delete Take; 28. delete Stop; 29. } 30. //+------------------------------------------------------------------+ 31. bool CheckCatch(ulong ticket) 32. { 33. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 34. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 35. if (ObjectFind(0, m_Infos.szShortName) >= 0) 36. { 37. m_Infos.ticket = 0; 38. return false; 39. } 40. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 41. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 42. 43. return true; 44. } 45. //+------------------------------------------------------------------+ 46. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 47. { 48. double value; 49. 50. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 51. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 52. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 53. switch (id) 54. { 55. case CHARTEVENT_CUSTOM + evUpdate_Position: 56. if (lparam != m_Infos.ticket) return; 57. if (!PositionSelectByTicket(m_Infos.ticket)) 58. { 59. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 60. return; 61. }; 62. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket)); 63. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket)); 64. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket)); 65. (*Open).UpdatePrice(PositionGetDouble(POSITION_PRICE_OPEN)); 66. if ((value = PositionGetDouble(POSITION_TP)) > 0) (*Take).UpdatePrice(value); else 67. { 68. delete Take; 69. Take = NULL; 70. } 71. if ((value = PositionGetDouble(POSITION_SL)) > 0) (*Stop).UpdatePrice(value); else 72. { 73. delete Stop; 74. Stop = NULL; 75. } 76. break; 77. } 78. ChartRedraw(); 79. } 80. //+------------------------------------------------------------------+ 81. }; 82. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Исходя из всего этого, пришло время наконец увидеть новый код класса 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. //+------------------------------------------------------------------+ 007. #define def_PathBtns "Images\\Market Replay\\Orders\\" 008. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 009. #resource "\\" + def_Btn_Close; 010. //+------------------------------------------------------------------+ 011. #include "..\Auxiliar\C_Mouse.mqh" 012. //+------------------------------------------------------------------+ 013. class C_ElementsTrade : private C_Mouse 014. { 015. private : 016. //+------------------------------------------------------------------+ 017. struct st00 018. { 019. ulong ticket; 020. string szPrefixName; 021. EnumEvents ev; 022. double price; 023. bool bClick; 024. }m_Info; 025. //+------------------------------------------------------------------+ 026. void UpdateViewPort(void) 027. { 028. int x, y; 029. 030. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 031. ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price); 032. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, 130); 033. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 034. } 035. //+------------------------------------------------------------------+ 036. public : 037. //+------------------------------------------------------------------+ 038. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 039. :C_Mouse(0, "") 040. { 041. string szObj; 042. 043. ZeroMemory(m_Info); 044. m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 045. CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio); 046. ObjectSetInteger(0, szObj, OBJPROP_WIDTH, 2); 047. ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr); 048. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 049. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 050. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 051. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 052. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 053. } 054. //+------------------------------------------------------------------+ 055. ~C_ElementsTrade() 056. { 057. if (m_Info.szPrefixName != "") 058. ObjectsDeleteAll(0, m_Info.szPrefixName); 059. } 060. //+------------------------------------------------------------------+ 061. inline void UpdatePrice(const double price) 062. { 063. m_Info.price = price; 064. UpdateViewPort(); 065. } 066. //+------------------------------------------------------------------+ 067. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 068. { 069. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 070. switch (id) 071. { 072. case CHARTEVENT_OBJECT_CLICK: 073. if ((m_Info.bClick) && (GetPositionsMouse().szObjNameClick == def_NameBtnClose)) switch (m_Info.ev) 074. { 075. case evMsgClosePositionEA: 076. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 077. break; 078. case evMsgCloseTakeProfit: 079. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 080. break; 081. case evMsgCloseStopLoss: 082. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 083. break; 084. } 085. m_Info.bClick = false; 086. break; 087. case CHARTEVENT_MOUSE_MOVE: 088. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 089. break; 090. case CHARTEVENT_CHART_CHANGE: 091. UpdateViewPort(); 092. break; 093. } 094. } 095. //+------------------------------------------------------------------+ 096. }; 097. //+------------------------------------------------------------------+ 098. #undef def_Btn_Close 099. #undef def_PathBtns 100. //+------------------------------------------------------------------+ 101. #undef def_NameBtnClose 102. #undef def_NameHLine 103. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Несколько из ранее размещенных элементов были удалены, что отчетливо заметно при сравнении обоих кодов. Однако обратите внимание, что в строке 23 появилась новая переменная. Она используется в процедуре DispatchMessage. В остальном ничего не изменилось. Но давайте посмотрим, как теперь будет работать процедура DispatchMessage. Такая работа обусловлена тем, что с этого момента эта процедура будет работать с помощью MetaTrader 5.
Обратите внимание, что в строке 67, где начинается процедура DispatchMessage, мы не внесли никаких существенных изменений. Мы просто адаптировали существующий код. Прошу заметить, что в обработчике события CHARTEVENT_MOUSE_MOVE, в строке 87, мы проверяем, наряду с индикатором мыши, является ли щелчок действительным или нет. Мы делаем это довольно простым и понятным способом. Таким образом, переменная из строки 23 получит значение true при действительном щелчке мыши или false, если индикатор мыши выполнит какое-либо анализ. Именно эту часть будет обрабатывать наше приложение. Область, где MetaTrader 5 может нам помочь, можно увидеть в событии CHARTEVENT_OBJECT_CLICK, из строки 72.
Обратите внимание на одну деталь в этом коде, а именно в событии CHARTEVENT_OBJECT_CLICK. Это в точности то же самое, что и в предыдущей статье. Однако это происходило в событии CHARTEVENT_MOUSE_MOVE. Но возникнут ли у нас здесь проблемы? На самом деле нет, потому что перед событием клика произойдет событие перемещения. И когда произойдет клик, мы проверим, является ли он действительным или нет. Будет выполнено только одно действие, как было показано в предыдущей статье, только при том условии, что мы имеем дело с валидным кликом, а имя объекта соответствует имени, обрабатываемому в данный момент. То есть, объект, представляющий собой кнопку закрытия. Всё остальное уже подробно объяснено, поэтому никаких дополнительных пояснений не требуется.
Хорошо, но прежде чем закончить данную статью, мы внесем небольшое изменение в систему. Это связано с тем, что нет особого смысла иметь горизонтальную линию во всю ширину графика, на которой в какой-то точке расположена кнопка закрытия. Мы можем улучшить это так, чтобы линия заканчивалась у кнопки закрытия, что будет немного логичнее. Однако это заложит основу для ещё одной модификации, которая будет внесена в будущем. Но, чтобы приблизиться к желаемому результату, нам придётся изменить класс. Поскольку я не совсем уверен, как всё будет складываться дальше, не стоит слишком привязываться к тому, что мы объясним, но постарайтесь понять, как всё работает. Возможно, это пригодится вам в будущем.
Подготовка к следующим шагам
Следующий шаг, который мы предпримем, может показаться несколько нелепым и даже ненужным. Однако это важно, учитывая то, что нам предстоит реализовать дальше. Итак, в чём же суть на данном этапе? Идея заключается в создании средства для визуальной и достаточно четкой проверки того, какой именно линией мы управляем. И почему это важно? Причина заключается в том, что мы можем использовать систему, в которой для захвата линии выполняется один клик, а чтобы отпустить её — ещё один. А без знания того, с какой именно линией мы работаем, очень сложно сделать это правильно. Следовательно, нам необходим простой и эффективный способ предоставления такой информации.
Многие могли бы подумать: "поскольку у нас открытая позиция, мы можем просто создать внутреннюю систему переключения". И я снова с вами согласен, уважаемый читатель. Внедрение внутренней системы переключения является наиболее разумным решением. Однако давайте немного обобщим. Подумайте о следующем: если вы используете счет типа HEDGING, система переключения будет недостаточно эффективной, так как на одном графике могут одновременно находиться два или более индикатора позиции. Это делает реализацию такого переключения довольно сложной, если учесть вероятность того, что вы кликните на линию одной позиции, а затем кликните на линию другой позиции. Иными словами, в этом случае мы столкнемся с затруднением.
Кроме того, существует ещё одна проблема, которая, пусть пока и не влияет на нашу работу, но создаст огромные трудности в будущем, если мы не реализуем архитектуру более универсальным способом. Речь идет об отложенных ордерах. В них будет использоваться система, очень похожая на ту, которую мы собираемся разработать сейчас. Следовательно, внутреннего переключателя для внесения соответствующих изменений будет недостаточно. Однако, к счастью, у нас есть столь же простое решение, которое полностью охватит все возможные сценарии. Это включает в себя передачу сообщений между индикаторами. Можно себе представить, что это чрезвычайно сложная и трудная для реализации задача. Но так ли это будет на самом деле? Прежде чем рассматривать необходимые изменения, давайте посмотрим, что изменилось в заголовочном файле Defines.mqh. Его можно увидеть ниже:
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 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position, //Event to communication 48. evMsgClosePositionEA, //Event to communication 49. evMsgCloseTakeProfit, //Event to communication 50. evMsgCloseStopLoss, //Event to communication 51. evMsgNewTakeProfit, //Event to communication 52. evMsgNewStopLoss, //Event to communication 53. evMsgSetFocus //Event to communication 54. }; 55. //+------------------------------------------------------------------+ 56. enum EnumPriority { //Priority list on objects 57. ePriorityNull = -1, 58. ePriorityDefault = 0 59. }; 60. //+------------------------------------------------------------------+
Defines.mqh
Обратите внимание, что в строке 53 определено новое событие. Это событие мы будем использовать для выполнения задачи, которую мы хотим реализовать. После этого мы можем перейти к коду класса 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. //+------------------------------------------------------------------+ 007. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 7 : 3)); 008. //+------------------------------------------------------------------+ 009. #define def_PathBtns "Images\\Market Replay\\Orders\\" 010. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 011. #resource "\\" + def_Btn_Close; 012. //+------------------------------------------------------------------+ 013. #include "..\Auxiliar\C_Mouse.mqh" 014. //+------------------------------------------------------------------+ 015. class C_ElementsTrade : private C_Mouse 016. { 017. private : 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szPrefixName; 023. EnumEvents ev; 024. double price; 025. bool bClick; 026. char weight; 027. }m_Info; 028. //+------------------------------------------------------------------+ 029. void UpdateViewPort(void) 030. { 031. int x, y; 032. 033. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 034. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 200 : 250)); 035. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 036. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight / 2)); 037. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 038. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 039. } 040. //+------------------------------------------------------------------+ 041. public : 042. //+------------------------------------------------------------------+ 043. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 044. :C_Mouse(0, "") 045. { 046. string szObj; 047. 048. ZeroMemory(m_Info); 049. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 050. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault)); 051. macro_LineInFocus(false); 052. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _color); 053. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 054. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 055. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 056. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 057. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 058. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 059. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 060. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 061. } 062. //+------------------------------------------------------------------+ 063. ~C_ElementsTrade() 064. { 065. ObjectsDeleteAll(0, m_Info.szPrefixName); 066. } 067. //+------------------------------------------------------------------+ 068. inline void UpdatePrice(const double price) 069. { 070. m_Info.price = price; 071. UpdateViewPort(); 072. } 073. //+------------------------------------------------------------------+ 074. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 075. { 076. string sz0; 077. 078. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 079. switch (id) 080. { 081. case CHARTEVENT_CUSTOM + evMsgSetFocus: 082. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 083. break; 084. case CHARTEVENT_OBJECT_CLICK: 085. sz0 = GetPositionsMouse().szObjNameClick; 086. if (m_Info.bClick) switch (m_Info.ev) 087. { 088. case evMsgClosePositionEA: 089. if (sz0 == def_NameBtnClose) 090. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 091. else if (sz0 == def_NameHLine) 092. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 093. break; 094. case evMsgCloseTakeProfit: 095. if (sz0 == def_NameBtnClose) 096. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 097. else if (sz0 == def_NameHLine) 098. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 099. break; 100. case evMsgCloseStopLoss: 101. if (sz0 == def_NameBtnClose) 102. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 103. else if (sz0 == def_NameHLine) 104. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 105. break; 106. } 107. m_Info.bClick = false; 108. break; 109. case CHARTEVENT_MOUSE_MOVE: 110. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 111. break; 112. case CHARTEVENT_CHART_CHANGE: 113. UpdateViewPort(); 114. break; 115. } 116. } 117. //+------------------------------------------------------------------+ 118. }; 119. //+------------------------------------------------------------------+ 120. #undef macro_LineInFocus 121. //+------------------------------------------------------------------+ 122. #undef def_Btn_Close 123. #undef def_PathBtns 124. //+------------------------------------------------------------------+ 125. #undef def_NameBtnClose 126. #undef def_NameHLine 127. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
А ожидаемый результат можно посмотреть в следующей анимации.

Я показываю этот результат перед объяснением кода, чтобы вы, уважаемый читатель, обратили внимание на то, что будет объясняться далее. Данный механизм может претерпеть некоторые изменения в будущем. Однако, если в какой-либо момент вам понадобится сделать что-то подобное, следует знать, что существует несколько способов это сделать. Но вам нужно понимать, почему данные способы работают. С учетом сказанного, думаю, вам уже любопытно понять, как удалось реализовать предыдущую анимацию без создания обычного механизма переключения. Давайте тогда разберемся, как и почему это происходит.
Прежде всего обратите внимание, что в строке 07 мы определили макрос для весьма очевидного выделения линии, которая получает фокус. В строке 26 я определил новую переменную, которая будет хранить значение толщины линии, определенной в этом классе. В строке 36 мы расположим созданную здесь линию по центру. Для правильного выполнения данной операции толщина линии должна быть задана нечетным значением. Если задать четное значение, например, два или четыре, линия будет немного смещена относительно идеальной позиции. Обратите внимание еще на один момент: в строке 34 мы определяем местоположение каждой из кнопок. Это предотвращает их чрезмерное сближение или перекрытие. Это затруднит правильный доступ к ним. Данные значения не являются окончательными, но они уже выполняют свою функцию.
Пока всё хорошо. Теперь обратите внимание на изменение в конструкторе класса. Ранее для обозначения ценовой линии использовался объект HLINE. Но теперь мы будем использовать объект OBJ_RECTANGLE_LABEL. Это изменение позволяет нам установить максимальный предел для размера горизонтальных линий. Таким образом, они не будут растягиваться от края до края на графике. Причина использования OBJ_RECTANGLE_LABEL, а не OBJ_TREND, заключается именно в том, что линиям тренда необходима привязка ко времени. Это то, что в определенные моменты оказывается сложно настроить правильно. В отличие от этого, объект OBJ_RECTANGLE_LABEL использует декартовы координаты. Это вполне подходит для наших целей.
Практически весь код остался прежним, несмотря на различие в объекте, который будет нашей горизонтальной линией. Однако реальные изменения произошли в процедуре строки 74, то есть в функции DispatchMessage. На данном этапе поначалу всё может показаться немного запутанным, но нет причин для паники. Просто будьте внимательны, и всё станет ясно.
Обратите внимание, что в строке 81 происходит обработка сообщения, созданного нами в файле Defines.mqh. Однако это сообщение существует здесь лишь потому, что мы обобщаем вещи до крайности. Прошу заметить, что обработка сообщений очень проста. Если класс C_ElementsTrade получает это сообщение, evMsgSetFocus, он проверяет, совпадает ли значение lparam с тикетом, отслеживаемым классом. Кроме того, параметр dparam должен быть одним из значений события. Если этого не произойдет, линия будет перенастроена на меньшую толщину. Если условие выполнено, толщина линии будет увеличена. Всё просто.
"Но подождите немного. Как класс поймет, когда линия станет толще или тоньше? Разве это не должно быть где-нибудь определено?" Да, уважаемый читатель. Фактическую задачу указания на это выполняют события, описанные в строках 92, 98 и 104. Каждое из них срабатывает только при наличии действительного щелчка мыши по индикатору. Обратите внимание, что условием, позволяющим запускать данные события, является именно имя объекта, на который был нажат щелчок. "Да, но почему именно так? Разве не было бы быстрее, проще и практичнее использовать макрос вместо запуска этих событий?" И снова вы правы, уважаемый читатель.
Однако это привело бы нас именно к той проблеме, которая возникает, когда на графике присутствует более одного индикатора позиции. Или когда у нас будут индикаторы отложенных ордеров. В данном случае один индикатор не сможет сообщить другому, что тот потерял фокус и больше не находится в центре внимания. Но, используя именно этот механизм передачи сообщений, мы позволяем всем индикаторам работать в полной гармонии и точно знать, что нужно делать, а что нет. Хотя данный механизм может быть не очень эффективным с точки зрения реализации, он достаточно практичен и безопасен. Кроме того, с точки зрения реализации он значительно проще, чем более эффективный механизм.
Заключительные идеи
Вы можете лично убедиться в работе данного механизма, используя демо-счет. Я рекомендую использовать демо-счет MetaQuotes, чтобы вы могли увидеть работу механизма при наличии нескольких открытых позиций. Поверьте, это будет очень интересно для изучения.
Если вы это сделаете, то, вероятно, заметите, что возникают некоторые трудности с выделением линий, даже если они толще 1 пикселя. Иногда линии бывает довольно трудно выделить. Чтобы ещё больше упростить задачу, мы немного усовершенствуем их в следующей статье. Кроме того, разумеется, мы начнем работать непосредственно на графике, без необходимости использования системы терминала, для создания линий стоп-лосса и тейк-профита.
В прикрепленном файле вы найдете исполняемые файлы, чтобы вы могли поэкспериментировать с пройденным материалом. Это для тех, кто не знает, как скомпилировать всю систему.
| Файл | Описание |
|---|---|
| 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/13274
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Кооперация бионических алгоритмов — Co-Operation of Biology Related Algorithms (COBRA)
Автоматизация торговых стратегий на MQL5 (Часть 36): Торговля по зонам спроса и предложения на основе модели ретеста и импульса
Анализ CSV-данных (Часть 6): Нормализация результатов по нескольким брокерам и сверка CSV между платформами
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования