Моделирование рынка: Position View (XII)
Введение
Приветствую всех в очередной статье из серии о том, как создать систему повторения/моделирования.
В предыдущей статье Моделирование рынка: Position View (XI) мы рассмотрели, как перемещать линии стоп-лосса и тейк-профита очень простым и практичным способом, отображая на графике именно то состояние, которое зарегистрировано на реальном торговом сервере. Как вы, вероятно, заметили, существует очевидная трудность, которая влияет на индикатор позиции, когда удалены и линия стоп-лосса, и линия тейк-профита. Проблема в том, что на графике невозможно напрямую определить, является ли позиция длинной или короткой. Для этого необходимо открыть терминал. Однако мы можем решить данную проблему очень простым и практичным способом. Это первое, что мы рассмотрим в этой статье. Давайте перейдём к первой теме.
Графическое представление направления позиции
Возможно, самый интересный аспект этой статьи — это не сами индикаторы и не система репликации/моделирования, а способ модификации кода для добавления новых функций или адаптации его под конкретную задачу.
В предыдущей статье я показал, как перемещать линии стоп-лосса и тейк-профита с минимальными усилиями. Многие считают это очень сложной и трудной задачей, учитывая количество необходимых действий. Без должных мер предосторожности график может отображать уровень цены, отличающийся от зарегистрированного на сервере. Это может произойти, если вы переместите линию на новый уровень, но сервер не зарегистрирует изменение. В результате, в реальной торговле можно понести значительные убытки и получить лишь незначительную прибыль. Нужно по возможности минимально вмешиваться в код и после каждой правки проверять, что система ведёт себя именно так, как ожидается.
Следуя той же философии — вносить как можно меньше изменений, не нарушая поведение системы, — мы добавим в индикатор позиции визуальный сигнал, который будет показывать прямо на графике, является ли позиция длинной или короткой.
Первое, что нам нужно сделать, — изменить класс C_ElementsTrade, добавив в него графический объект, который и укажет, позиция длинная или короткая. Это простая и понятная задача. Приведённый ниже код демонстрирует новую версию класса 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. //+------------------------------------------------------------------+ 009. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 010. //+------------------------------------------------------------------+ 011. #define def_PathBtns "Images\\Market Replay\\Orders\\" 012. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 013. #resource "\\" + def_Btn_Close; 014. //+------------------------------------------------------------------+ 015. #include "..\Auxiliar\C_Mouse.mqh" 016. //+------------------------------------------------------------------+ 017. class C_ElementsTrade : private C_Mouse 018. { 019. private : 020. //+------------------------------------------------------------------+ 021. struct st00 022. { 023. ulong ticket; 024. string szPrefixName, 025. szDescr; 026. EnumEvents ev; 027. double price; 028. bool bClick, 029. bIsBuy; 030. char weight; 031. color _color; 032. }m_Info; 033. //+------------------------------------------------------------------+ 034. void UpdateViewPort(void) 035. { 036. int x, y; 037. 038. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 039. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 040. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 041. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 042. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 044. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 18); 045. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 046. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 047. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateLinePrice(void) 051. { 052. string szObj; 053. 054. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 055. macro_LineInFocus(false); 056. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 057. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 058. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 059. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 060. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 061. } 062. //+------------------------------------------------------------------+ 063. inline void CreateBoxMove(void) 064. { 065. string szObj; 066. 067. CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 068. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 069. ObjectSetString(0, szObj, OBJPROP_TEXT, "u"); 070. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17); 071. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 072. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21); 073. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23); 074. } 075. //+------------------------------------------------------------------+ 076. inline void CreateButtonClose(void) 077. { 078. string szObj; 079. 080. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 081. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 082. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 083. } 084. //+------------------------------------------------------------------+ 085. inline void CreateInfoDirect(void) 086. { 087. string szObj; 088. const char c[] = {(char)(m_Info.bIsBuy ? 236 : 238), 0}; 089. 090. CreateObjectGraphics(szObj = def_NameInfoDirect, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 091. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 092. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 093. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 15); 094. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 095. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21); 096. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23); 097. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (m_Info.bIsBuy ? clrForestGreen : clrFireBrick)); 098. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 099. } 100. //+------------------------------------------------------------------+ 101. public : 102. //+------------------------------------------------------------------+ 103. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, string szDescr = "\n", const bool IsBuy = true) 104. :C_Mouse(0, "") 105. { 106. ZeroMemory(m_Info); 107. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 108. m_Info._color = _color; 109. m_Info.szDescr = szDescr; 110. m_Info.bIsBuy = IsBuy; 111. } 112. //+------------------------------------------------------------------+ 113. ~C_ElementsTrade() 114. { 115. ObjectsDeleteAll(0, m_Info.szPrefixName); 116. } 117. //+------------------------------------------------------------------+ 118. inline void UpdatePrice(const double open, const double price) 119. { 120. m_Info.price = (price > 0 ? price : open); 121. if (price > 0) 122. { 123. CreateLinePrice(); 124. CreateButtonClose(); 125. }else 126. ObjectsDeleteAll(0, m_Info.szPrefixName); 127. if (m_Info.ev != evMsgClosePositionEA) CreateBoxMove(); 128. else CreateInfoDirect(); 129. UpdateViewPort(); 130. } 131. //+------------------------------------------------------------------+ 132. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 133. { 134. string sz0; 135. 136. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 137. switch (id) 138. { 139. case CHARTEVENT_CUSTOM + evMsgSetFocus: 140. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 141. m_Info.bClick = false; 142. case CHARTEVENT_CHART_CHANGE: 143. UpdateViewPort(); 144. break; 145. case CHARTEVENT_OBJECT_CLICK: 146. sz0 = GetPositionsMouse().szObjNameClick; 147. if (m_Info.bClick) switch (m_Info.ev) 148. { 149. case evMsgClosePositionEA: 150. if (sz0 == def_NameBtnClose) 151. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 152. break; 153. case evMsgCloseTakeProfit: 154. if (sz0 == def_NameBtnClose) 155. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 156. else if (sz0 == def_NameBtnMove) 157. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 158. break; 159. case evMsgCloseStopLoss: 160. if (sz0 == def_NameBtnClose) 161. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 162. else if (sz0 == def_NameBtnMove) 163. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 164. break; 165. } 166. m_Info.bClick = false; 167. break; 168. case CHARTEVENT_MOUSE_MOVE: 169. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 170. if (m_Info.bClick && (m_Info.weight > 1)) 171. { 172. switch (m_Info.ev) 173. { 174. case evMsgCloseTakeProfit: 175. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 176. break; 177. case evMsgCloseStopLoss: 178. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 179. break; 180. } 181. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 182. } 183. break; 184. } 185. } 186. //+------------------------------------------------------------------+ 187. }; 188. //+------------------------------------------------------------------+ 189. #undef macro_LineInFocus 190. //+------------------------------------------------------------------+ 191. #undef def_Btn_Close 192. #undef def_PathBtns 193. //+------------------------------------------------------------------+ 194. #undef def_NameInfoDirect 195. #undef def_NameBtnMove 196. #undef def_NameBtnClose 197. #undef def_NameHLine 198. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Если вам не удаётся идентифицировать изменения, я выделю некоторые конкретные модификации, чтобы сделать их более очевидными. Однако в идеале вы, уважаемый читатель, должны попытаться понять код без необходимости читать объяснения, которые я собираюсь предоставить. Так вы привыкнете понимать код и самостоятельно определять те части, которые могут вас заинтересовать. Отлично, в строке 07 мы находим определение имени, которое получит создаваемый объект. В строке 29 мы определяем новую переменную. Пока что он будет использоваться очень редко, поэтому может показаться излишним объявлять его. Позже вы лучше поймёте причину его объявления. Теперь, переходя к процедуре для строки 34, мы обнаруживаем две новые строки: 44 и 45. Они определяют положение объекта на графике.
Теперь перейдём к строке 85, где процедура CreateInfoDirect создаёт графический объект. Есть одна деталь, которую нам нужно понять. Если вы решите использовать изображение для представления длинной или короткой позиции, эту процедуру, начиная со строки 85, потребуется слегка изменить. В этом нет ничего сложного. Используйте код CreateButtonClose в качестве основы для загрузки изображения. Однако здесь мы будем использовать решение, аналогичное тому, которое использовалось в процедуре CreateBoxMove. Другими словами, мы поставим символ, который указывает, является ли позиция длинной или короткой. Один важный момент: на данный момент большая часть кода CreateBoxMove также будет присутствовать в CreateInfoDirect. Здесь показано, как создавать данные элементы. Впоследствии это дублирование кода исчезнет.
Давайте теперь проанализируем этот раздел, поскольку некоторые инструкции могут быть сложными для понимания. Например, выражение в строке 88 может вызвать вопросы. Теперь ответьте честно: когда вы смотрите на данную строку 88, понимаете ли вы, что происходит? Если это именно так, то всё в порядке. Это значит, что вы уже знаете то, что я собираюсь объяснить. Если это не так, то будьте внимательны. Строка 92 присваивает графическому объекту последовательность символов. Чтобы сгенерировать её, функция CharArrayToString преобразует массив "с" в строку, определённую в строке 88.
Почему я определяю это значение как константу? Потому что хочу убедиться в том, что случайно не изменю значение этого массива, и в том, что это останется неизменным на протяжении выполнения всей процедуры. Хотя данная процедура проста, в будущем она может немного усложниться. И я не хочу рисковать случайно изменить значение массива "c". А теперь начинается волшебство. Строка в MQL5 следует тому же принципу, что и строки в C/C++, то есть заканчивается нулевым символом, или \0. Это нулевое значение, которое вы видите в строке 88. Массив "c" — это одномерный динамический массив, начальный размер которого составляет два элемента: код отображаемого символа и завершающий ноль. Его динамический характер обусловлен тем, что первое измерение объявлено без фиксированного размера; начальный размер, напротив, определяется двумя значениями, включёнными в инициализацию. Без этого синтаксиса код завершится ошибкой во время компиляции.
То есть, в объявлении мы определяем "c" как одномерный динамический массив, элементы которого имеют тип ushort. После знака равенства начинается инициализация, ограниченная фигурными скобками. Внутри этих фигурных скобок выражение (ushort) не объявляет переменную и не изменяет объявление массива: оно явно преобразует к типу ushort результат выражения, вычисляемого с помощью тернарного оператора. Данное преобразование предотвращает генерацию предупреждений компилятором во время компиляции. Здесь мы используем значение переменной, определенной в строке 29. Если оно истинно, первый элемент массива хранит числовой код 236; если ложно — хранит числовой код 238. Сами по себе эти числа не являются текстом: они идентифицируют символы. Функция CharArrayToString преобразует массив в строку, содержащую выбранный символ. Строка 91 назначает Wingdings в качестве шрифта графического объекта, поэтому шрифт отображает этот символ с помощью глифа, соответствующего коду 236 или 238. Чтобы узнать, какой символ появится на графике, проверьте эти коды в таблице символов Wingdings. Остальная часть процедуры использует стандартные операции MQL5 для создания и настройки графических объектов.
Хорошо, вы, вероятно, задаётесь вопросом: "как мы укажем классу, какой из двух глифов он должен отображать для представления длинной или короткой позиции?" Ответ прост: мы передадим направление позиции через конструктор. По этой причине рассмотрите код конструктора в строке 103. Обратите внимание, что он был слегка изменён, но это не настолько существенно, чтобы требовать отдельного объяснения. Поскольку данный конструктор был изменен, нам также необходимо изменить код, который его использует. Прежде чем мы перейдем к рассмотрению изменений, давайте ещё раз взглянем на них. Я имею в виду код в строке 118. Строка 128 отличает линию цены открытия от линий тейк-профита и стоп-лосса. Условие в строке 127 определяет, представляет ли экземпляр линию цены открытия или одну из линий тейк-профита и стоп-лосса. Когда экземпляр представляет цену открытия, код не создаёт графический объект для перемещения линии. Вместо этого он создаёт объект, который показывает, является ли позиция длинной или короткой. Таким образом, мы можем завершить разработку кода для класса C_ElementsTrade. Как я недавно упоминал, произошло изменение в коде, который вызывает конструктор. Следовательно, нам необходимо изменить код класса C_IndicatorPosition. Поскольку большая часть кода осталась нетронутой, мы сосредоточимся только на том, что изменилось. Итак, изменённая часть показана в следующем фрагменте:
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, StringFormat("%I64u : Position opening price.", m_Infos.ticket), PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY); 63. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, StringFormat("%I64u : Take Profit price.", m_Infos.ticket)); 64. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket)); 65. (*Open).UpdatePrice(0, value = PositionGetDouble(POSITION_PRICE_OPEN)); 66. (*Take).UpdatePrice(value, PositionGetDouble(POSITION_TP)); 67. (*Stop).UpdatePrice(value, PositionGetDouble(POSITION_SL)); 68. break; 69. } 70. ChartRedraw(); 71. } 72. //+------------------------------------------------------------------+
Фрагмент файла C_IndicatorPosition.mqh
Обратите внимание, насколько незначительны эти изменения. Это происходит именно в строках с 62 по 64. Данное изменение единственное по сравнению с тем, что было раньше. После компиляции индикатора позиции и его размещения на графике появится графический сигнал, похожий на тот, что показан на следующем изображении:

Выделенная область на этом изображении показывает различие. Теперь на графике четко видно, является ли позиция длинной или короткой. Здорово, не правда ли? Всё довольно просто. Прежде чем перейти к следующему изменению, давайте посмотрим, как выглядит окончательный код класса 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. //+------------------------------------------------------------------+ 009. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 010. //+------------------------------------------------------------------+ 011. #define def_PathBtns "Images\\Market Replay\\Orders\\" 012. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 013. #resource "\\" + def_Btn_Close; 014. //+------------------------------------------------------------------+ 015. #include "..\Auxiliar\C_Mouse.mqh" 016. //+------------------------------------------------------------------+ 017. class C_ElementsTrade : private C_Mouse 018. { 019. private : 020. //+------------------------------------------------------------------+ 021. struct st00 022. { 023. ulong ticket; 024. string szPrefixName, 025. szDescr; 026. EnumEvents ev; 027. double price; 028. bool bClick, 029. bIsBuy; 030. char weight; 031. color _color; 032. }m_Info; 033. //+------------------------------------------------------------------+ 034. void UpdateViewPort(void) 035. { 036. int x, y; 037. 038. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 039. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 040. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 041. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 042. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 044. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 18); 045. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 046. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 047. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateLinePrice(void) 051. { 052. string szObj; 053. 054. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 055. macro_LineInFocus(false); 056. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 057. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 058. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 059. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 060. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 061. } 062. //+------------------------------------------------------------------+ 063. inline void CreateBoxInfo(const bool bMove) 064. { 065. string szObj; 066. const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0}; 067. 068. CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 069. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 070. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 071. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? m_Info._color : (m_Info.bIsBuy ? clrForestGreen : clrFireBrick))); 072. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15)); 073. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 074. } 075. //+------------------------------------------------------------------+ 076. inline void CreateButtonClose(void) 077. { 078. string szObj; 079. 080. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 081. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 082. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 083. } 084. //+------------------------------------------------------------------+ 085. public : 086. //+------------------------------------------------------------------+ 087. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, string szDescr = "\n", const bool IsBuy = true) 088. :C_Mouse(0, "") 089. { 090. ZeroMemory(m_Info); 091. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 092. m_Info._color = _color; 093. m_Info.szDescr = szDescr; 094. m_Info.bIsBuy = IsBuy; 095. } 096. //+------------------------------------------------------------------+ 097. ~C_ElementsTrade() 098. { 099. ObjectsDeleteAll(0, m_Info.szPrefixName); 100. } 101. //+------------------------------------------------------------------+ 102. inline void UpdatePrice(const double open, const double price) 103. { 104. m_Info.price = (price > 0 ? price : open); 105. if (price > 0) 106. { 107. CreateLinePrice(); 108. CreateButtonClose(); 109. }else 110. ObjectsDeleteAll(0, m_Info.szPrefixName); 111. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 112. UpdateViewPort(); 113. } 114. //+------------------------------------------------------------------+ 115. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. string sz0; 118. 119. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 120. switch (id) 121. { 122. case CHARTEVENT_CUSTOM + evMsgSetFocus: 123. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 124. m_Info.bClick = false; 125. case CHARTEVENT_CHART_CHANGE: 126. UpdateViewPort(); 127. break; 128. case CHARTEVENT_OBJECT_CLICK: 129. sz0 = GetPositionsMouse().szObjNameClick; 130. if (m_Info.bClick) switch (m_Info.ev) 131. { 132. case evMsgClosePositionEA: 133. if (sz0 == def_NameBtnClose) 134. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 135. break; 136. case evMsgCloseTakeProfit: 137. if (sz0 == def_NameBtnClose) 138. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 139. else if (sz0 == def_NameBtnMove) 140. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 141. break; 142. case evMsgCloseStopLoss: 143. if (sz0 == def_NameBtnClose) 144. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 145. else if (sz0 == def_NameBtnMove) 146. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 147. break; 148. } 149. m_Info.bClick = false; 150. break; 151. case CHARTEVENT_MOUSE_MOVE: 152. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 153. if (m_Info.bClick && (m_Info.weight > 1)) 154. { 155. switch (m_Info.ev) 156. { 157. case evMsgCloseTakeProfit: 158. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 159. break; 160. case evMsgCloseStopLoss: 161. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 162. break; 163. } 164. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 165. } 166. break; 167. } 168. } 169. //+------------------------------------------------------------------+ 170. }; 171. //+------------------------------------------------------------------+ 172. #undef macro_LineInFocus 173. //+------------------------------------------------------------------+ 174. #undef def_Btn_Close 175. #undef def_PathBtns 176. //+------------------------------------------------------------------+ 177. #undef def_NameInfoDirect 178. #undef def_NameBtnMove 179. #undef def_NameBtnClose 180. #undef def_NameHLine 181. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Чтобы подстегнуть ваше желание учиться, я не буду объяснять, что именно было изменено и почему. Я хочу, чтобы вы изучили и попытались понять изменения, внесенные в приведенный выше код для удаления дублирующихся частей. Ваша задача — понять, почему данные изменения не повлияли на то, что показано на графике.
Прежде чем завершить эту статью, давайте внесём в систему ещё одно обновление, чтобы всё стало ещё более интересным. Это обновление позволит нам узнать, настраиваем ли мы линию тейк-профита или линию стоп-лосса. Давайте рассмотрим это в рамках новой темы.
Какую линию я перемещаю: тейк-профита или стоп-лосса?
Совершенно нормально, что после клика по графическому объекту вы сомневаетесь, какую из двух линий вы перемещаете: тейк-профита или стоп-лосса. На самом деле, сомнение может возникнуть даже после клика по графику, поскольку до сих пор не существует никакого способа узнать, перемещаем ли мы одну из ценовых линий или просто перемещаем курсор мыши по графику. Кроме того, если мы перемещаем ценовую линию и забываем снова нажать на нее, нас может ждать неприятный сюрприз при нажатии на определенную позицию на графике. Для решения данной проблемы мы продолжим совершенствовать систему. Правда пока улучшение будет очень простым и лишь покажет, какую из двух ценовых линий мы перемещаем: тейк-профит или стоп-лосс.
Что ж, уважаемый читатель, в данный момент вы, возможно, думаете следующее: теперь перед нами проблема, решение которой потребует много работы. Однако на самом деле это не требует больших усилий. Если вы следили за этой серией статей о системе репликации/моделирования, вы быстро поймёте стратегию, которую мы применим. Прежде чем продолжить, необходимо понять, как индикатор мыши, советник и индикатор позиции обмениваются событиями.
Я сделаю краткое обобщение, но советую вам прочитать предыдущие статьи, если вы ещё этого не сделали, чтобы понять всё полностью. Иначе вы не поймете тех изменений, которые я собираюсь внести.
Взаимодействия распределены между несколькими компонентами графика, которые обмениваются событиями и обрабатывают их скоординировано. Другими словами, у нас есть индикатор мыши, советник и индикатор позиции. Кроме того, конечно же, есть индикатор Chart Trade. Однако он не будет участвовать в изменениях, которые мы внесем ниже. Когда мы кликаем по элементу управления, который завершает построение, по ценовой линии или по графическому представлению позиции, Эксперт обрабатывает событие, а индикатор позиции обновляет график. При клике по графическому объекту, который позволяет перемещать линию тейк-профита или стоп-лосса, горизонтальная линия индикатора мыши следует за курсором.
Второй клик отправляет советнику окончательный уровень цены для этой линии. Индикатор мыши передает выбранный ценовой уровень индикатору позиции, который генерирует событие, получаемое советником. Затем советник запрашивает у торгового сервера обновление уровня цены для линии тейк-профита или стоп-лосса. Уровень линии остаётся прежним до тех пор, пока сервер не подтвердит изменение. После получения подтверждения советник отправляет проверенный уровень на индикатор позиции, который обновляет линию на графике. Таким образом, видимый результат зависит от данной последовательности событий и ответа сервера. Первое, что нам нужно сделать, — отправить каждому компоненту соответствующее сообщение для обновления его состояния, не изменяя текущего хода событий. Чтобы вам было понятнее то, как происходит взаимодействие, мы будем вносить изменения шаг за шагом. Сначала мы сделаем следующее: когда вы кликнете по одному из графических объектов для перемещения ценовой линии, мы скроем горизонтальную линию индикатора мыши. Как только вы выберете новый уровень цены для линии тейк-профита или стоп-лосса, мы снова покажем горизонтальную линию индикатора мыши. Проще некуда.
Если вы не следите за этой серией статей, у вас может возникнуть соблазн изменить объекты на графике. Хотя это само по себе не является неправильным, это будет считаться ошибкой, потому что мы не должны манипулировать этими объектами, а должны уведомлять приложения о том, что им следует изменить своё поведение. Это правильный путь. Следовательно, для реализации такого поведения потребуется немного изменить код класса C_ElementsTrade. Изменение настолько тонкое, что, если вы не будете внимательны, вы его не заметите. Это показано в следующем фрагменте:
114. //+------------------------------------------------------------------+ 115. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. string sz0; 118. 119. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 120. switch (id) 121. { 122. case CHARTEVENT_CUSTOM + evMsgSetFocus: 123. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 124. EventChartCustom(0, (ushort)(dparam ? evHideMouse : evShowMouse), 0, 0, ""); 125. m_Info.bClick = false; 126. case CHARTEVENT_CHART_CHANGE: 127. UpdateViewPort(); 128. break; 129. case CHARTEVENT_OBJECT_CLICK: 130. sz0 = GetPositionsMouse().szObjNameClick; 131. if (m_Info.bClick) switch (m_Info.ev) 132. {
Фрагмент из C_ElementsTrade
Предыдущий фрагмент наглядно демонстрирует, какие именно изменения нам необходимо внести. Вот именно: мы модифицируем процедуру DispatchMessage. Если не присматриваться, то сложно заметить, где именно находится модификация. Это прямо на строке 124. Данная строка передает индикатору мыши значение перечисления, определяющее, следует ли отображать или скрывать горизонтальную линию. Проще некуда. Обратите внимание, что выражение (ushort) не является объявлением, а явным приведением типа, применяемым к значению перечисления, которое обычно имеет тип int. Нам нужно преобразовать это значение в ushort; в противном случае компилятор предупредит о возможной потере данных.
На этом завершается первая модификация. В режиме перемещения ценовой линии горизонтальная линия индикатора мыши будет скрыта. В противном случае горизонтальная линия снова будет показана на графике. Давайте перейдём ко второй модификации. Мы сделаем это следующим образом. Мы заменим горизонтальную линию индикатора мыши на вспомогательную ценовую линию, которая будет представлять ту линию, которую мы перемещаем. Для этого нам потребуется внести некоторые дополнительные изменения в код. Тем не менее, мы продолжим работу над классом C_ElementsTrade. Давайте теперь немного подумаем об этом.
Как мы хотим представить данную вспомогательную ценовую линию? На мой взгляд, лучший способ — использовать оригинальные цвета линий. Так будет ясно, что мы перемещаем именно нужную линию. Есть ещё один момент. Простое перемещение линии кажется слишком лёгким и даже скучным. Что вы скажете, если мы немного усложним задачу и также переместим графические элементы управления, связанные с этой линией, такие как кнопка закрытия и элемент управления, используемый для её перемещения? "Хм, теперь это кажется немного сложнее. Неужели это действительно так?" Хорошо, давайте посмотрим. Если идея заключается в перемещении всех объектов вместе с ценовой линией, достаточно будет передать новый уровень цены в процедуру UpdateViewPort. "Хм, с таким кодом это будет невозможно". А что произойдет, если мы изменим процедуру UpdateViewPort таким образом, чтобы она получала цену?" Новый код показан в следующем фрагменте:
33. //+------------------------------------------------------------------+ 34. void UpdateViewPort(const double price) 35. { 36. int x, y; 37. 38. ChartTimePriceToXY(0, 0, 0, price, x, y); 39. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 40. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 41. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 42. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 43. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 44. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 18); 45. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 46. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 47. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 48. } 49. //+------------------------------------------------------------------+
Фрагмент из C_ElementsTrade
Хорошо. В связи с этим нам нужно модифицировать процедуру, ответственную за обновление цены. Это показано в следующем фрагменте:
101. //+------------------------------------------------------------------+ 102. inline void UpdatePrice(const double open, const double price) 103. { 104. if (price > 0) 105. { 106. CreateLinePrice(); 107. CreateButtonClose(); 108. }else 109. ObjectsDeleteAll(0, m_Info.szPrefixName); 110. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 111. UpdateViewPort(m_Info.price = (price > 0 ? price : open)); 112. } 113. //+------------------------------------------------------------------+
Фрагмент из C_ElementsTrade
Итак, чего же мы этим добьемся? Это на самом деле не решает проблему. Мы по-прежнему не можем перемещать ценовую линию так, как рассчитывали. Однако, если мы теперь изменим процедуру DispatchMessage, как показано ниже, ситуация уже изменится.
113. //+------------------------------------------------------------------+ 114. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 115. { 116. string sz0; 117. 118. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 119. switch (id) 120. { 121. case CHARTEVENT_CUSTOM + evMsgSetFocus: 122. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 123. EventChartCustom(0, (ushort)(dparam ? evHideMouse : evShowMouse), 0, 0, ""); 124. m_Info.bClick = false; 125. case CHARTEVENT_CHART_CHANGE: 126. UpdateViewPort(m_Info.price); 127. break; 128. case CHARTEVENT_OBJECT_CLICK: 129. sz0 = GetPositionsMouse().szObjNameClick; 130. if (m_Info.bClick) switch (m_Info.ev) 131. { 132. case evMsgClosePositionEA: 133. if (sz0 == def_NameBtnClose) 134. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 135. break; 136. case evMsgCloseTakeProfit: 137. if (sz0 == def_NameBtnClose) 138. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 139. else if (sz0 == def_NameBtnMove) 140. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 141. break; 142. case evMsgCloseStopLoss: 143. if (sz0 == def_NameBtnClose) 144. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 145. else if (sz0 == def_NameBtnMove) 146. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 147. break; 148. } 149. m_Info.bClick = false; 150. break; 151. case CHARTEVENT_MOUSE_MOVE: 152. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 153. if (m_Info.weight > 1) 154. { 155. UpdateViewPort(GetPositionsMouse().Position.Price); 156. if (m_Info.bClick) 157. { 158. switch (m_Info.ev) 159. { 160. case evMsgCloseTakeProfit: 161. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 162. break; 163. case evMsgCloseStopLoss: 164. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 165. break; 166. } 167. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 168. } 169. } 170. break; 171. } 172. } 173. //+------------------------------------------------------------------+
Фрагмент из C_ElementsTrade
Теперь обратите внимание. Когда линия выделена, переменная weight будет иметь значение больше единицы. Следовательно, логическое выражение в строке 153 вернёт true, и программа выполнит соответствующий блок. Внутри этого блока строка 155 вызывает процедуру UpdateViewPort и передаёт ей новый предварительный ценовой уровень для линии. Отображение обновляется в реальном времени, но изменение подтверждается только при нажатии. Как только сервер подтвердит уровень цен, система возобновит работу.
Иными словами, мы модифицировали код, не изменив наблюдаемое поведение системы. Как вы уже убедились, это было совсем несложно. Нам потребовалось скорректировать всего несколько инструкций, чтобы UpdateViewPort обновляла линию и связанные с ней графические элементы управления. Ничего сложного, честно говоря, это даже немного скучно. Теперь мы можем рассмотреть более сложную ситуацию. А может, и нет? Хорошо, давайте подумаем о следующем. Что произойдет, если во время перемещения ценовой линии мы решим провести исследование? Это должно остановить текущее движение и вернуть объекты в исходное положение. Возможно, вы думаете, что это очень сложно, поскольку значения изменились во время первых перемещений.
Давайте посмотрим, как решить данную проблему. Итак, когда мы начинаем перемещать одну из ценовых линий, будь то тейк-профит или стоп-лосс, мы не можем начать исследование. Чтобы начать исследование с использованием индикатора мыши, обратитесь к предыдущим статьям, чтобы узнать, как это сделать. Для продолжения нужно будет отменить перемещение и решить, как его осуществить. После разговора с некоторыми людьми они предложили мне использовать нечто довольно практичное и надёжное: нажатие клавиши ESC. Мы точно можем случайно нажать кнопку мыши или другой элемент управления, но мы точно не нажмем клавишу ESC, не заметив этого. Следовательно, мы снова модифицируем процедуру DispatchMessage, чтобы клавиша ESC отменяла перемещение. После этого можно выполнить любое другое действие.
113. //+------------------------------------------------------------------+ 114. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 115. { 116. string sz0; 117. long _lparam = lparam; 118. double _dparam = dparam; 119. 120. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 121. switch (id) 122. { 123. case (CHARTEVENT_KEYDOWN): 124. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 125. _lparam = (long) m_Info.ticket; 126. _dparam = 0; 127. case CHARTEVENT_CUSTOM + evMsgSetFocus: 128. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 129. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 130. m_Info.bClick = false; 131. case CHARTEVENT_CHART_CHANGE: 132. UpdateViewPort(m_Info.price); 133. break; 134. case CHARTEVENT_OBJECT_CLICK: 135. sz0 = GetPositionsMouse().szObjNameClick; 136. if (m_Info.bClick) switch (m_Info.ev)
Фрагмент из C_ElementsTrade
Предыдущий фрагмент демонстрирует решение. Я не включил весь код процедуры DispatchMessage, потому что, на мой взгляд, это совершенно не нужно, поскольку решение легко можно увидеть в этом фрагменте. Обратите внимание, что потребовалось добавить две локальные переменные, чтобы сохранить неизменными значения параметров события, полученных от MetaTrader 5. Эти переменные находятся в строках 117 и 118. Теперь возникает вполне резонный вопрос: почему бы вам не убрать квалификатор const из аргументов вызова в строке 114? Таким образом, отпала бы необходимость использовать данные переменные. Во многих случаях это было бы допустимым решением. Однако, по опыту знаю, что так делать не стоит, потому что у вас часто будет возникать соблазн изменить это значение. В какой-то момент в будущем процедура может перестать правильно интерпретировать параметры события, отправленные MetaTrader 5 или операционной системой. В Windows этот же шаблон параметров используется для обмена сообщениями между приложениями или процессами. Это чаще всего встречается при программировании DLL. По этой причине я не советую вам вносить такое изменение. Предпочтительнее всегда изменять значение локально, как показано здесь. Это избавит вас от головной боли и потерь времени на попытки понять, почему код работает неправильно.
Хорошо, после этого обратите внимание, что строка 123 определяет события клавиатуры, полученные процедурой. Существует несколько способов управления и фильтрации данных событий. В MetaTrader 5 строка 124 сравнивает полученный код клавиши с кодом ESC. Если клавиша не ESC, команда break завершает обработку события в этой строке. Например, если вы выбрали объект для перемещения и нажимаете F2, блок отмены не выполнится. Если полученная клавиша — ESC, программа выполнит строки с 125 по 133. В этом случае выполнение намеренно продолжается через два следующих блока обработки, что позволяет повторно использовать их логику без дублирования кода.
Заключительные идеи
Я думаю, что на данный момент мы добились довольно значительного прогресса. На данном этапе вам, уважаемый читатель, уже многое предстоит понять и попробовать. Поскольку вы, возможно, не знаете, как компилировать эти файлы MQL5, в приложении приведены предварительно скомпилированные версии. Если вы только начинаете, можно проверить работу описанной выше системы.
Помните, что всё показанное здесь предназначено для образовательных целей. Поэтому вы должны действовать с особой осторожностью при использовании прилагаемых приложений на реальном счёте. Я бы посоветовал использовать их на демо-счетах. Узнайте, как работает система, и модифицируйте её в соответствии с вашими конкретными потребностями.
Увидимся в следующей статье, где мы продолжим продвигаться к более полной системе.
| Файл | Описание |
|---|---|
| Experts\Expert Advisor.mq5 | Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study). |
| Indicators\Chart Trade.mq5 | Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study) |
| Indicators\Market Replay.mq5 | Создает элементы управления для взаимодействия со сервисом репликации/моделирования (для взаимодействия требуется Mouse Study). |
| Indicators\Mouse Study.mq5 | Обеспечивает взаимодействие между графическими элементами управления и пользователем (необходимое как для работы системы репликации/моделирования, так и на реальном рынке). |
| Indicators\Order Indicator.mq5 | Отвечает за отображение рыночных ордеров и позволяет взаимодействовать с ними и управлять ими |
| Indicators\Position View.mq5 | Отвечает за отображение рыночных позиций и позволяет взаимодействовать с ними и управлять ими |
| Services\Market Replay.mq5 | Создает и поддерживает сервис репликации/моделирования рынка (главный файл всей системы). |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/13317
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Автоматизация торговых стратегий в MQL5 (Часть 43): Адаптивная стратегия на основе канала линейной регрессии
VQC-фильтр с живым обучением прямо в MQL5 поверх графового fair-value скальпера
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования