Español Português
preview
Моделирование рынка: Position View (XI)

Моделирование рынка: Position View (XI)

MetaTrader 5Тестер |
58 0
Daniel Jose
Daniel Jose

Введение

Здравствуйте, дорогие друзья! Добро пожаловать в очередную статью из серии о том, как создать систему репликации/моделирования.

В предыдущей статье Моделирование рынка: Position View (X) мы объяснили, как мы можем выбирать объекты, которые мы создаём на графике с помощью MetaTrader 5. До этого момента главной задачей было найти удовлетворительный способ выбора объектов на графике. Без помощи MetaTrader 5 и использования свойства ZOrder эта задача была бы довольно сложной. Но, как уже было показано в предыдущей статье, мы нашли довольно практичный и эффективный способ использовать MetaTrader 5 в своих интересах.

Как только нам удалось добиться этого результата, стало ясно, что нам предстоит решить ещё одну проблему. Проблема заключается именно в выборе ценовых линий. Несмотря на то, что они относительно толще, это всё равно непростая задача. Особенно когда это нужно сделать довольно быстро и линии расположены очень близко друг к другу.

Правильно выбрать нужную линию и сделать это максимально быстро крайне необходимо, но также мы должны подходить к этому обдуманно. Мы не только будем выбирать линию, но хотим также предоставить возможность создания ценовой линии непосредственно на графике, будь то линия тейк-профита или стоп-лосса. Для трейдера это взаимодействие должно быть максимально простым. Не с точки зрения кода, а с точки зрения трейдера.

Некоторое время назад в этих статьях мы представили модель, которая, на мой взгляд, вполне подходит для наших нужд. Однако данная модель была полностью связана с советником, поэтому извлечь её из внутренней структуры было сложно. Но поскольку это имеет отношение к нашим потребностям, мы объясним, как это реализовать. Однако сейчас мы сделаем это в виде индикатора. Итак, начнём.


Создание кнопки для перемещения цен

Идея этой довольно старой модели заключалась в использовании специальной кнопки для выбора нужной линии. Помимо возможности выбора линии, эта кнопка также позволяла создать её, если она отсутствовала на графике, а также выполняла другие небольшие функции, которые мы также рассмотрим в данной статье. Таким образом, первое, что нужно сделать, показано в следующем коде:

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. //+------------------------------------------------------------------+
008. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
009. //+------------------------------------------------------------------+
010. #define def_PathBtns "Images\\Market Replay\\Orders\\"
011. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
012. #resource "\\" + def_Btn_Close;
013. //+------------------------------------------------------------------+
014. #include "..\Auxiliar\C_Mouse.mqh"
015. //+------------------------------------------------------------------+
016. class C_ElementsTrade : private C_Mouse
017. {
018.     private    :
019. //+------------------------------------------------------------------+
020.         struct st00
021.         {
022.             ulong         ticket;
023.             string        szPrefixName;
024.             EnumEvents    ev;
025.             double        price;
026.             bool          bClick;
027.             char          weight;
028.         }m_Info;
029. //+------------------------------------------------------------------+
030.         void UpdateViewPort(void)
031.         {
032.             int x, y;
033.             
034.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);            
035.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
036.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
037.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
038.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
039.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
040.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30);
041.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
042.         }
043. //+------------------------------------------------------------------+
044. inline void CreateLinePrice(const color _color, const string szDescr)
045.         {
046.             string szObj;
047.             
048.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault));
049.             macro_LineInFocus(false);
050.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _color);
051.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
052.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
053.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
054.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr);
055.         }
056. //+------------------------------------------------------------------+
057. inline void CreateBoxMove(const color _color)
058.         {
059.             string szObj;
060.             
061.             CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, _color, (EnumPriority)(ePriorityDefault));
062.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
063.             ObjectSetString(0, szObj, OBJPROP_TEXT, "u");
064.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17);
065.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
066.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21);
067.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23);
068.         }
069. //+------------------------------------------------------------------+
070. inline void CreateButtonClose(void)
071.         {
072.             string szObj;
073.             
074.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
075.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
076.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
077.         }
078. //+------------------------------------------------------------------+
079.     public    :
080. //+------------------------------------------------------------------+
081.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
082.             :C_Mouse(0, "")
083.         {        
084.             ZeroMemory(m_Info);
085.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
086.             CreateLinePrice(_color, szDescr);
087.             if (ev != evMsgClosePositionEA) CreateBoxMove(_color);
088.             CreateButtonClose();
089.         }
090. //+------------------------------------------------------------------+
091.         ~C_ElementsTrade()
092.         {
093.             ObjectsDeleteAll(0, m_Info.szPrefixName);
094.         }
095. //+------------------------------------------------------------------+
096. inline void UpdatePrice(const double price)
097.         {
098.             m_Info.price = price;
099.             UpdateViewPort();
100.         }
101. //+------------------------------------------------------------------+
102.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
103.         {
104.             string sz0;
105.             
106.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
107.             switch (id)
108.             {
109.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
110.                     macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev));
111.                     m_Info.bClick = false;
112.                 case CHARTEVENT_CHART_CHANGE:
113.                     UpdateViewPort();
114.                     break;
115.                 case CHARTEVENT_OBJECT_CLICK:
116.                     sz0 = GetPositionsMouse().szObjNameClick;
117.                     if (m_Info.bClick) switch (m_Info.ev)
118.                     {
119.                         case evMsgClosePositionEA:
120.                             if (sz0 == def_NameBtnClose)
121.                                 EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
122.                             break;
123.                         case evMsgCloseTakeProfit:
124.                             if (sz0 == def_NameBtnClose)
125.                                 EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
126.                             else if (sz0 == def_NameBtnMove)
127.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, "");
128.                             break;
129.                         case evMsgCloseStopLoss:
130.                             if (sz0 == def_NameBtnClose)
131.                                 EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
132.                             else if (sz0 == def_NameBtnMove)
133.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, "");
134.                             break;
135.                     }
136.                     m_Info.bClick = false;
137.                     break;
138.                 case CHARTEVENT_MOUSE_MOVE:
139.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
140.                     if (m_Info.bClick && (m_Info.weight > 1)) EventChartCustom(0, evMsgSetFocus, 0, 0, "");
141.                     break;
142.             }
143.         }
144. //+------------------------------------------------------------------+
145. };
146. //+------------------------------------------------------------------+
147. #undef macro_LineInFocus
148. //+------------------------------------------------------------------+
149. #undef def_Btn_Close
150. #undef def_PathBtns
151. //+------------------------------------------------------------------+
152. #undef def_NameBtnMove
153. #undef def_NameBtnClose
154. #undef def_NameHLine
155. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

Здесь мы начинаем дальнейшее совершенствование управления объектами. Обратите внимание, что в строке 06 мы создали новое определение. Оно даст имя объекту, который будет представлять собой нашу иконку перемещения. Пока этот элемент отсутствует, перемещать линию нельзя. Когда он существует, линию можно перемещать. Однако на данный момент мы не будем беспокоиться о перемещении линии. Сначала рассмотрим, как управлять выбором.

Обратите внимание, что код изменился очень незначительно. Это позволит вам по-настоящему понять весь процесс шаг за шагом. Таким образом, при желании можно легко изменить то, что мы показываем.

В строке 40 мы указываем местоположение нашего нового объекта на оси X. Обратите внимание, что мы оставили небольшой отступ между этим объектом и кнопкой закрытия. А теперь внимание: в строке 35 я изменил смещение объектов таким образом, чтобы они не перекрывались. Это оставляет между ними небольшое расстояние, поэтому у нас не возникнет проблем, когда данный индикатор будет отображаться на графике.

Чтобы ещё больше упростить будущие изменения, я вынес из конструктора процедуры, создающие объекты. Так появились процедуры CreateLinePrice и CreateButtonClose. Содержание данных процедур ранее было заложено в конструктор. Поэтому я не буду вдаваться в дальнейшие подробности о них. Однако процедура CreateBoxMove является новой, поэтому заслуживает базового объяснения. В данной процедуре мы просто создаём объект типа OBJ_LABEL и присваиваем ему текст. На самом деле, всего один символ. Он будет указывать, можем ли мы выбрать и, следовательно, переместить ценовую линию. Поскольку код очень простой, я не думаю, что требуется подробное объяснение.

Теперь взгляните на следующие детали в конструкторе. Если быть точнее, посмотрите на строку 87. В этой строке мы проверяем, является ли созданный элемент управления линией закрытия позиции. Если это не так, мы можем создать объект перемещения ценовой линии. Отлично, теперь мы можем перейти к процедуре DispatchMessage. В ходе данной процедуры произошли некоторые более существенные изменения. Но они направлены на предотвращение дублирования кода, поскольку некоторые задачи можно решать каскадным образом.

Вы можете увидеть это, обратив внимание на событие в строке 109: в этом событии, в строке 111, мы присваиваем значение false состоянию клика мыши. Но это событие, связанное со строкой 109, закончится не так, как многие ожидали. На самом деле, произойдет переход к следующему элементу, CHARTEVENT_CHART_CHANGE, который находится в строке 112. Но зачем это делать или допускать? Причина проста. При изменении толщины линии необходимо перерисовать её на графике. Если бы мы не позволили пользовательскому событию перейти к событию перерисовки, нам пришлось бы добавить специальный вызов для перерисовки линии в пользовательском событии, и это мы можем исключить, поскольку мы просто выстраиваем каскадную обработку событий.

Обратите внимание, что в событии CHARTEVENT_OBJECT_CLICK мы не проверяем, был ли нажат клик на ценовую линию. Теперь мы проверяем, был ли выполнен щелчок по объекту перемещения. Поэтому не тратьте время на выбор ценовой линии. Нажмите непосредственно на маленький ромб, который появится на нём. Таким образом, линия будет выбрана вместе с ромбом.

Ещё одна новая функция находится именно в событии CHARTEVENT_MOUSE_MOVE. Обратите внимание, что теперь в строке 140 выполняется новая проверка. Она используется для того, чтобы убедиться, что на графике не выбраны другие линии. Если данное условие выполнится, будет запущено событие для удаления выбора. Такая реализация довольно любопытна, поскольку если есть выбранная линия и вы кликаете в любую точку графика, выделение с линии снимается. И нам не придётся беспокоиться о том, какая линия будет выбрана. Со всех линий будет снято выделение.

Хорошо, но хотя то, что я только что показал, можно использовать для выбора или маркировки ценовой линии, это не позволяет сделать ничего более полезного. Давайте немного подумаем. Я вам гарантирую, это будет очень интересно.

Глядя на приведённый выше код и читая объяснение его работы, можете ли вы предложить простой и быстрый способ перемещения этих ценовых линий? Неважно, примет это торговый сервер или нет. Я просто хочу, чтобы вы сказали мне, возможно ли переместить линии с помощью того, что мы только что реализовали. Помните, что для того, чтобы это стало возможным, нам нужно добавить код в систему. Вопрос в том, сможем ли мы сделать это с минимальным количеством кода. Мне не хочется писать много кода. Итак, возможно сделать это или нет?

Если вы ответили НЕТ, вы, возможно, не совсем понимаете, как всё это работает. Вероятно, вы подумаете, что это невозможно сделать без спокойного обдумывания, но мы можем это сделать. Разумеется, при условии, что мы добавим немного больше кода в нашу основную систему. Напомним, что в предыдущих статьях мы уже добавили в систему два новых сообщения. Однако эти сообщения тогда ещё не были реализованы в коде, поскольку в то время это было нецелесообразно. Теперь же у нас есть простой и быстрый способ выбора ценовых линий. Таким образом, мы можем использовать данную систему обмена сообщениями и реализовать её. Прежде чем мы это сделаем, я хочу объяснить, что мы собираемся предпринять. Так вы сможете понять или представить себе реализацию решения, прежде чем ее увидеть.

Если заменить заголовочный файл C_ElementsTrade.mqh кодом, который мы рассматривали в начале данной темы, вы увидите, что при выборе ценовой линии и щелчке по точке с линии снимается выделение. Именно в этом и заключается идея. Когда мы запрашиваем у индикатора позиции снять выделение с ценовой линии, он должен отправить сообщение советнику, указывая на то, что отмеченная линия должна быть перемещена в новое положение. Эта новая позиция будет точно соответствовать точке, в которой происходит щелчок мыши. Теперь вы понимаете?

Тем не менее, идея не доведена до совершенства. Но сейчас мы об этом беспокоиться не будем, мы просто сделаем то, что я сказал: когда выбрана линия цены и мы щелкаем по другой точке, эта линия должна переместиться в эту точку. Этого можно добиться, изменив код процедуры DispatchMessage следующим образом:

138.     case CHARTEVENT_MOUSE_MOVE:
139.         m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
140.         if (m_Info.bClick && (m_Info.weight > 1))
141.         {
142.             switch (m_Info.ev)
143.             {
144.                 case evMsgCloseTakeProfit:
145.                     EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
146.                     break;
147.                 case evMsgCloseStopLoss:
148.                     EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
149.                     break;
150.             }
151.             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
152.         }
153.         break;

Фрагмент файла C_ElementsTrade.mqh

Посмотрите, что здесь произошло. Добавив данный фрагмент кода в класс C_ElementsTrade, можно перемещать линии тейк-профита и стоп-лосса с помощью индикатора мыши. Однако, взгляните на следующие детали. Мы не перемещаем линии или связанные с ними объекты, мы делаем именно то, что было запланировано. Иными словами, он отмечает линию, используя объект перемещения. Переместите индикатор мыши в новую позицию и щелкните ещё раз. Когда это произойдёт, проверка в строке 140 должна будет выполниться в какой-то момент.

Когда это произойдёт, в строке 142 выполняется выбор нужного события. В этом случае событие в строке 145 может быть запущено, если была выбрана линия тейк-профита, или событие в строке 148 может быть запущено, если была выбрана линия стоп-лосса. Обратите внимание, что сообщение, которое будет отправлено в MetaTrader 5, практически одинаково. Однако, оно отличается вторым параметром.

Это сообщение будет перенаправлено MetaTrader 5 на график, где загружен индикатор, и перехвачено советником. В коде класса C_Orders, входящего в состав советника, это сообщение будет интерпретировано как запрос на изменение положения ценовой линии, будь то стоп-лосса или тейк-профита. Как только сервер вернёт результат нашего запроса, советник снова отправит сообщение, которое будет перехвачено индикатором позиции, что заставит его отобразить новую точку, в которой находится ценовая линия. Таким образом, всякий раз, когда мы будем смотреть на график, мы будем точно знать, где находятся линии на сервере. Это происходит так, потому что мы всегда точно отражаем то, что находится на сервере. Вот так мы можем легко перемещать линии с минимально возможным количеством кода.

Эта система, которую мы только что реализовали, работает настолько хорошо, что вы даже можете использовать трейлинг-стоп, доступный в MetaTrader 5, чтобы линия стоп-лосса перемещалась, и этот индикатор позиции следовал за ней и оставался верным тому, что существует на торговом сервере.

Несмотря на то, что данная система работает превосходно, мы до сих пор не решили ещё одну проблему. Проблема в следующем: как мы можем создать линии тейк-профита или стоп-лосса, если они были удалены с сервера и, соответственно, с графика? Хорошо, это кажется гораздо сложнее. Но следуйте за нашим объяснением, и вы увидите, что это так же просто, как то, что мы только что сделали. Чтобы разделить темы и избежать путаницы, мы перейдем к новой теме.


Без линий стоп-лосса и тейк-профита. Как разместить их заново?

Теперь у нас несколько иная проблема. Она заключается в следующем: предположим, мы удалим значения стоп-лосса и тейк-профита на сервере, но спустя некоторое время мы решим вернуть их на свое место. Существует несколько способов это сделать. Однако здесь я хочу показать, как изменить индикатор позиции, чтобы снова установить стоп-лосс или тейк-профит непосредственно на графике.

Для этого нам потребуется немного доработать индикатор. Это связано с тем, что при удалении стоп-лосса или тейк-профита из позиции, индикатор также удаляет элементы, соответствующие линиям стоп-лосса или тейк-профита. Это то поведение, которое нам придётся изменить. Таким образом, мы сможем использовать тот же механизм, который мы рассмотрели в предыдущей теме. То есть щёлкните по одному из элементов, будь то стоп-лосс или тейк-профит. Переместите указатель мыши в нужную точку и кликните снова. Таким образом изменяется предельное значение для закрытия позиции. Это основная идея.

Но как это сделать легко? Возможно, вы придумали какое-нибудь довольно безумное решение. Я не знаю, будет ли это проще или сложнее, чем предложенное решение, но, как и в предыдущей теме, идея состоит в том, чтобы вносить в код как можно меньше изменений.

Хорошо, давайте ещё раз остановимся и немного подумаем: какое было бы самое простое решение нашей проблемы? Учитывая, что мы хотим использовать ту же модель, которую рассматривали в предыдущей теме, самым простым решением будет оставить объект перемещения ценовой линии на графике. Если вы так думали, значит, вы думали так же, как и я. Но где мы можем разместить объект, когда не существует ценовой линии, будь то тейк-профита или стоп-лосса? Хорошо, в этом случае, я думаю, лучшим решением будет разместить объект перемещения прямо на линии, где открылась позиция. Не знаю, разделяете ли вы это мнение, уважаемый читатель. Вы вольны думать так, как считаете нужным, при условии, конечно, что вы знаете, что делаете.

Давайте сделаем это таким образом. Когда линия стоп-лосса или тейк-профита снимается с позиции, индикатор перемещения размещается рядом с линией цены открытия. Но теперь у нас возникла проблема. При удалении этой же линии, индикатор позиции также удаляет связанные с ней объекты. Мы не хотим ещё больше усложнять наш код, поэтому давайте посмотрим, в какой части кода индикатор удаляет эти объекты. Это показано в следующем фрагменте.

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. //+------------------------------------------------------------------+

Фрагмент файла C_IndicatorPosition.mqh

Данный фрагмент является частью класса C_IndicatorPosition. Это исходный код данного класса. Теперь давайте рассмотрим следующие пункты. В строках 66 и 71 у нас две проверки: одна для тейк-профита, а другая для стоп-лосса. Теперь обратите внимание, чтобы понять, что нам нужно сделать. Данные проверки сообщают индикатору позиции, что нам необходимо удалить указатели на класс C_ElementsTrade. Это происходит, когда значение позиции равно или меньше нуля. Думаю, вы всё прекрасно поняли.

Однако в этом месте нам потребуется изменить код. Давайте подумаем об этом немного. Если вместо удаления указателя на класс C_ElementsTrade мы передадим значения линий тейк-профита и стоп-лосса в этот класс, то сможем создать некий механизм, который позволит нам сделать то, что мы хотим. Тогда этот же фрагмент должен быть изменён для реализации указанного механизма. Данная модификация показана ниже:

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(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

Обратите внимание, что код практически не изменился. Однако мы отключили выполняемые проверки. Теперь, независимо от значения стоп-лосса или тейк-профита, оно будет передано в класс C_ElementsTrade. Таким образом, мы сможем правильно управлять объектами ценовой линии.

Отлично, теперь давайте посмотрим, как выглядит код в классе C_ElementsTrade. Исходный код представлен в следующем фрагменте:

095. //+------------------------------------------------------------------+
096. inline void UpdatePrice(const double price)
097.         {
098.             m_Info.price = price;
099.             UpdateViewPort();
100.         }
101. //+------------------------------------------------------------------+

Фрагмент файла C_ElementsTrade.mqh

Теперь давайте сосредоточимся на предыдущем фрагменте, именно здесь произойдет волшебство. Однако обратите внимание, что изначально мы ожидали получить только один параметр. Но в изменениях, внесенных в класс C_IndicatorPosition, можно увидеть два параметра, которые передаются в эту процедуру. Это будет первое изменение. Но прежде чем посмотреть, как должен выглядеть код, давайте соберём ещё несколько деталей. Хорошо, первый полученный аргумент укажет местоположение линии цены открытия позиции. Второй аргумент — это цена линии, которую нужно создать.

А теперь подумайте немного. Нам нужно только удалить ненужные объекты, которыми в данном случае являются объект, представляющий ценовую линию, и кнопка закрытия, а кнопку перемещения оставить. Однако данный объект, представляющий собой перемещение, будет перемещен в новое положение. Данная новая позиция — это цена открытия позиции. Таким образом, самый простой способ внести это изменение — изменить предыдущий фрагмент так, чтобы он выглядел так, как показано ниже:

095. //+------------------------------------------------------------------+
096. inline void UpdatePrice(const double open, const double price)
097.         {
098.             m_Info.price = (price > 0 ? price : open);
099.             if (price == 0)
100.             {
101.                 ObjectDelete(0, def_NameBtnClose);
102.                 ObjectDelete(0, def_NameHLine);
103.             }
104.             UpdateViewPort();
105.         }
106. //+------------------------------------------------------------------+

Фрагмент файла C_ElementsTrade.mqh

Этот фрагмент позволил бы нашему индикатору позиции работать должным образом, но есть небольшой недостаток. Обратите внимание, что когда трейдер убирает линию стоп-лосса или тейк-профита с открытой позиции, индикатор позиции также убирает горизонтальную линию и кнопку закрытия. Это связано со строками 101 и 102 предыдущего фрагмента. Но когда трейдер запрашивает изменение позиции линии стоп-лосса или тейк-профита на открытой позиции, корректно будет перемещаться только кнопка перемещения. Остальные объекты будут удалены из списка объектов. И они ни при каких обстоятельствах не появятся на графике. Результат показан на следующем изображении:

Это не серьёзная проблема, а лишь небольшое неудобство, которое нужно исправить. Это так же просто, как и то, что мы делали до настоящего момента. Однако нам потребуется внести несколько более существенных изменений в код класса. Ниже представлен окончательный, уже исправленный код:

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. //+------------------------------------------------------------------+
008. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
009. //+------------------------------------------------------------------+
010. #define def_PathBtns "Images\\Market Replay\\Orders\\"
011. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
012. #resource "\\" + def_Btn_Close;
013. //+------------------------------------------------------------------+
014. #include "..\Auxiliar\C_Mouse.mqh"
015. //+------------------------------------------------------------------+
016. class C_ElementsTrade : private C_Mouse
017. {
018.     private    :
019. //+------------------------------------------------------------------+
020.         struct st00
021.         {
022.             ulong       ticket;
023.             string      szPrefixName,
024.                         szDescr;
025.             EnumEvents  ev;
026.             double      price;
027.             bool        bClick;
028.             char        weight;
029.             color       _color;
030.         }m_Info;
031. //+------------------------------------------------------------------+
032.         void UpdateViewPort(void)
033.         {
034.             int x, y;
035.             
036.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);            
037.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
038.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
039.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
040.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
041.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
042.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30);
043.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
044.         }
045. //+------------------------------------------------------------------+
046. inline void CreateLinePrice(void)
047.         {
048.             string szObj;
049.             
050.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
051.             macro_LineInFocus(false);
052.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._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, m_Info.szDescr);
057.         }
058. //+------------------------------------------------------------------+
059. inline void CreateBoxMove(void)
060.         {
061.             string szObj;
062.             
063.             CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
064.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
065.             ObjectSetString(0, szObj, OBJPROP_TEXT, "u");
066.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17);
067.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
068.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21);
069.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23);
070.         }
071. //+------------------------------------------------------------------+
072. inline void CreateButtonClose(void)
073.         {
074.             string szObj;
075.             
076.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
077.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
078.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
079.         }
080. //+------------------------------------------------------------------+
081.     public    :
082. //+------------------------------------------------------------------+
083.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
084.             :C_Mouse(0, "")
085.         {        
086.             ZeroMemory(m_Info);
087.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
088.             m_Info._color = _color;
089.             m_Info.szDescr = szDescr;
090.         }
091. //+------------------------------------------------------------------+
092.         ~C_ElementsTrade()
093.         {
094.             ObjectsDeleteAll(0, m_Info.szPrefixName);
095.         }
096. //+------------------------------------------------------------------+
097. inline void UpdatePrice(const double open, const double price)
098.         {
099.             m_Info.price = (price > 0 ? price : open);
100.             if (price > 0)
101.             {
102.                 CreateLinePrice();
103.                 CreateButtonClose();
104.             }else
105.                 ObjectsDeleteAll(0, m_Info.szPrefixName);
106.             if (m_Info.ev != evMsgClosePositionEA) CreateBoxMove();
107.             UpdateViewPort();
108.         }
109. //+------------------------------------------------------------------+
110.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
111.         {
112.             string sz0;
113.             
114.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
115.             switch (id)
116.             {
117.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
118.                     macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev));
119.                     m_Info.bClick = false;
120.                 case CHARTEVENT_CHART_CHANGE:
121.                     UpdateViewPort();
122.                     break;
123.                 case CHARTEVENT_OBJECT_CLICK:
124.                     sz0 = GetPositionsMouse().szObjNameClick;
125.                     if (m_Info.bClick) switch (m_Info.ev)
126.                     {
127.                         case evMsgClosePositionEA:
128.                             if (sz0 == def_NameBtnClose)
129.                                 EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
130.                             break;
131.                         case evMsgCloseTakeProfit:
132.                             if (sz0 == def_NameBtnClose)
133.                                 EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
134.                             else if (sz0 == def_NameBtnMove)
135.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, "");
136.                             break;
137.                         case evMsgCloseStopLoss:
138.                             if (sz0 == def_NameBtnClose)
139.                                 EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
140.                             else if (sz0 == def_NameBtnMove)
141.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, "");
142.                             break;
143.                     }
144.                     m_Info.bClick = false;
145.                     break;
146.                 case CHARTEVENT_MOUSE_MOVE:
147.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
148.                     if (m_Info.bClick && (m_Info.weight > 1))
149.                     {
150.                         switch (m_Info.ev)
151.                         {
152.                             case evMsgCloseTakeProfit:
153.                                 EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
154.                                 break;
155.                             case evMsgCloseStopLoss:
156.                                 EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
157.                                 break;
158.                         }
159.                         EventChartCustom(0, evMsgSetFocus, 0, 0, "");
160.                     }
161.                     break;
162.             }
163.         }
164. //+------------------------------------------------------------------+
165. };
166. //+------------------------------------------------------------------+
167. #undef macro_LineInFocus
168. //+------------------------------------------------------------------+
169. #undef def_Btn_Close
170. #undef def_PathBtns
171. //+------------------------------------------------------------------+
172. #undef def_NameBtnMove
173. #undef def_NameBtnClose
174. #undef def_NameHLine
175. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

Результат выполнения данного кода показан в следующей анимации.

Обратите внимание, что система теперь работает безупречно, всё так, как и ожидалось. Поскольку код претерпел некоторые изменения, многие из которых очень просты, нет необходимости выделять их отдельно. Однако, уважаемый читатель, я хочу, чтобы вы обратили особое внимание на два момента в данном коде. Первый — это конструктор. Обратите внимание: он немного отличается от предыдущей версии. Вторая процедура — UpdatePrice. Обратите внимание, что процедуры, создававшие объекты, которые раньше находились в конструкторе, теперь находятся в этой процедуре. Поскольку код довольно простой, нет необходимости вдаваться в детали. Фактически всё, что было сделано, — удалено то, что делалось в конструкторе, и перенесено в данную процедуру. Ничего особо примечательного.

Однако, я хочу, чтобы вы обратили внимание на кое-что другое, в частности, на анимацию выше. Ответьте мне честно. Если бы вы, как трейдер, попытались использовать только что реализуемую нами систему, стали бы вы сомневаться при корректировке уровня тейк-профита или уровня стоп-лосса? Знаете ли вы, нужно ли вести линию индикатора мыши к более высокой или более низкой цене, чтобы снова разместить ценовую линию на сервере? Возможно, последний вопрос был недостаточно ясен. Давайте попробуем немного прояснить ситуацию, чтобы вы поняли, какую проблему нам ещё предстоит решить.

Глядя только на анимацию, можно чётко увидеть, что мы находимся в короткой позиции. Это связано с тем, что линия тейк-профита находится ниже цены открытия позиции. Хорошо, но предположим, что вы, как трейдер, удалили бы обе линии с графика. Или, точнее, что вы удалили их из позиции, зарегистрированной на сервере. Тогда, глядя на эту же анимацию и наблюдая только за индикатором позиции, вы не смогли бы определить, короткая это позиция или длинная. Это первый момент.

Второй момент заключается в следующем: если мы продали и значения тейк-профита и стоп-лосса не определены в позиции, то есть оба объекта, позволяющие перемещать ценовую линию, находятся на цене открытия, как бы вы могли определить, глядя только на график, нужно ли размещать линию стоп-лосса выше или ниже текущей цены? На этот вопрос сложно ответить, не правда ли? Однако у нас есть довольно простое решение данной проблемы. Как видите, в классе C_ElementsTrade мы никогда не обращаемся к данным о позиции. Данные поступают из класса C_IndicatorPosition. И я не хочу загрязнять класс C_ElementsTrade данными, которые в будущем пришлось бы из него удалять. Это связано с тем, что мы будем использовать его и при работе с отложенными ордерами. Поэтому нам придётся приложить немного больше усилий. Мы рассмотрим это в следующей статье.


Заключительные идеи

В этой статье я показал вам как можно изменить индикатор позиции, чтобы он был способен выполнять гораздо больше действий, чем изначально, без особых усилий. Мы увидели, как реализовать возможность перемещения цен и создания ценовых линий непосредственно на графике. Многим это покажется сложным для решения. Однако, как вы, возможно, заметили, мы всё делали очень легко и с минимальными усилиями. Всё, что потребовалось, это остановиться и немного подумать.

Именно в этом и заключается цель этой серии статей. Я здесь не для того, чтобы учить вас создавать приложения. Однако изучение программирования становится гораздо более интересным, когда мы видим, что создаём что-то новое. Я надеюсь, что вы поймете цель этих статей и что они послужат хорошей теоретической и мотивационной основой для дальнейшего обучения. Потому что не бывает неразрешимых проблем, а существуют люди, которые не могут увидеть простое решение для больших проблем.

Файл Описание
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/13295

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Автоматизация торговых стратегий на MQL5 (Часть 40): Торговля по уровням коррекции Фибоначчи с пользовательскими уровнями Автоматизация торговых стратегий на MQL5 (Часть 40): Торговля по уровням коррекции Фибоначчи с пользовательскими уровнями
В статье разрабатывается советник MQL5 для торговли по уровням коррекции Фибоначчи. Пользовательские уровни, например 50% и 61,8%, рассчитываются по диапазону дневной свечи или массиву данных за выбранный период. Направление определяется по соотношению цен закрытия и открытия; позиции открываются при пересечении уровней с ограничением числа сделок, необязательным закрытием при новом расчёте, трейлинг-стопом в пунктах и буферами SL/TP в процентах от диапазона.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Создание торговых систем с искусственным интеллектом на MQL5 (Часть 4): Преодоление ограничений многострочного ввода, сохранение чатов между сеансами и генерация торговых сигналов Создание торговых систем с искусственным интеллектом на MQL5 (Часть 4): Преодоление ограничений многострочного ввода, сохранение чатов между сеансами и генерация торговых сигналов
В этой статье мы улучшим программу с интеграцией ChatGPT на MQL5: устраним ограничения многострочного ввода благодаря улучшенной отрисовке текста, добавим боковую панель для навигации по истории сохраненных чатов с шифрованием AES256 и ZIP-сжатием, а также сформируем начальные торговые сигналы на основе данных графика.