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

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

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

Введение

Всем привет и добро пожаловать в новую статью из серии, посвященной созданию сервиса репликации/моделирования.

В предыдущей статье Моделирование рынка: Position View (XVIII) я показал, как решать некоторые ситуации. Например, я объяснил, как предотвратить удаление пользователем или трейдером графических объектов, созданных индикатором позиции. Мы сделали это очень просто, без особых сложностей, поскольку повторное размещение объектов в нужном порядке может оказаться довольно трудоемким и потребовать от нас настоящих программистских ухищрений. Кроме того, мы добавили отображение объема — показатель, который некоторые трейдеры используют, чтобы понять, применима ли к данной позиции та или иная стратегия

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

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

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


Освободить класс C_ElementsTrade от зависимостей

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

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

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

240. //+------------------------------------------------------------------+
241.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
242.         {
243.             string sz0;
244.             long _lparam = lparam;
245.             double _dparam = dparam;
246.             
247.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
248.             switch (id)
249.             {
250.                 case (CHARTEVENT_KEYDOWN):
251.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
252.                     _lparam = (long) m_Info.ticket;
253.                     _dparam = 0;
254.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
255.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
256.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
257.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
258.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
259.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
260.                     m_Info.bClick = false;
261.                 case CHARTEVENT_CHART_CHANGE:
262.                     ChartChange();
263.                     break;
264.                 case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
265.                     m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03);
266.                     for (int c0 = PositionsTotal() - 1; c0 >= 0; c0--)
267.                         EventChartCustom(0, evUpdate_Position, PositionGetTicket(c0), 0, NULL);
268.                     break;
269.                 case CHARTEVENT_OBJECT_CLICK:
270.                     sz0 = GetPositionsMouse().szObjNameClick;
271.                     if (m_Info.bClick)
272.                     {
273.                         if (sz0 == def_NameBtnMove)
274.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
275.                         if (sz0 == def_NameBtnClose)
276.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL));
277.                         if (sz0 == def_NameObjLabel)
278.                             EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL);
279.                     }
280.                     m_Info.bClick = false;
281.                     break;
282.                 case CHARTEVENT_MOUSE_MOVE:
283.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
284.                     if (m_Info.weight > 1)
285.                     {
286.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
287.                         if (m_Info.ev != evMsgClosePositionEA)
288.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
289.                         if (m_Info.bClick)
290.                         {
291.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
292.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
293.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
294.                         }
295.                     }
296.                     break;
297.                 case CHARTEVENT_OBJECT_DELETE:
298.                     if (StringFind(sparam, m_Info.szPrefixName) < 0) break;
299.                     UpdatePrice(m_Info.open, m_Info.price);
300.                     break;
301.             }
302.         }
303. //+------------------------------------------------------------------+

Фрагмент из C_ElementsTrade

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

Для этого нам нужно изменить код класса, как показано в следующем фрагменте.

026. //+------------------------------------------------------------------+
027. class C_ElementsTrade : private C_Mouse
028. {
029.     private    :
030. //+------------------------------------------------------------------+
031.         struct stInfos
032.         {
033.             struct st_01
034.             {
035.                 short   Width,
036.                         Height,
037.                         digits;
038.             }Text;
039.             ulong       ticket;
040.             string      szPrefixName,
041.                         szDescr,
042.                         szSymbol;
043.             EnumEvents  ev;
044.             double      price,
045.                         open,
046.                         volume,
047.                         var,
048.                         tickSize;
049.             bool        bClick,
050.                         bIsBuy;
051.             char        weight;
052.             color       _color;
053.             int         sizeText;
054.             enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;
055.         }m_Info;
056. //+------------------------------------------------------------------+

                                   .
                                   .
                                   .

169. //+------------------------------------------------------------------+
170.     public    :
171. //+------------------------------------------------------------------+
172.         C_ElementsTrade(const ulong ticket, string szSymbol, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true)
173.             :C_Mouse(0, "")
174.         {
175.             ZeroMemory(m_Info);
176.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
177.             m_Info._color = _color;
178.             m_Info.szDescr = szDescr;
179.             m_Info.bIsBuy = IsBuy;
180.             m_Info.Text.digits = digits;
181.             m_Info.tickSize = ticksize;
182.             m_Info.szSymbol = szSymbol;
183.         }
184. //+------------------------------------------------------------------+

                                   .
                                   .
                                   .
                                   
242. //+------------------------------------------------------------------+
243.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
244.         {

                                   .
                                   .
                                   .

266.                 case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
267.                     m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03);
268.                     for (int c0 = PositionsTotal() - 1; c0 >= 0; c0--)
269.                         EventChartCustom(0, evUpdate_Position, PositionGetTicket(c0), 0, NULL);
270.                     break;
271.                 case CHARTEVENT_OBJECT_CLICK:
272.                     sz0 = GetPositionsMouse().szObjNameClick;
273.                     if (m_Info.bClick)
274.                     {
275.                         if (sz0 == def_NameBtnMove)
276.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
277.                         if (sz0 == def_NameBtnClose)
278.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), m_Info.szSymbol);
279.                         if (sz0 == def_NameObjLabel)
280.                             EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL);
281.                     }
282.                     m_Info.bClick = false;
283.                     break;
284.                 case CHARTEVENT_MOUSE_MOVE:
285.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
286.                     if (m_Info.weight > 1)
287.                     {
288.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
289.                         if (m_Info.ev != evMsgClosePositionEA)
290.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
291.                         if (m_Info.bClick)
292.                         {
293.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
294.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, m_Info.szSymbol);
295.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
296.                         }
297.                     }
298.                     break;

Фрагмент класса C_ElementsTrade

В этом фрагменте я оставил только те части, которые нужны, чтобы вы могли увидеть, где были внесены изменения. Начнем со строки 42, где объявляется новая переменная для хранения имени символа. Как я уже отмечал ранее, конструктор будет получать имя символа. Это видно в строке 172. В строке 182 мы сохраняем значение, переданное вызывающей стороной. Затем в DispatchMessage мы заменяем вызовы PositionGetString значением переменной, объявленной в строке 42. Тем самым мы решаем первую из этих проблем. Пока не беспокойтесь об основном коде индикатора. Когда мы устраним все оставшиеся вопросы и зависимости класса C_ElementsTrade, посмотрим, как будет выглядеть новый основной код.

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

266.                 case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
267.                     m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03);
268.                     EventChartCustom(0, evUpdate_Position, m_Info.ticket, 0, NULL);
269.                     break;

Фрагмент класса C_ElementsTrade

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

Когда мы генерируем пользовательское событие для изменения режима отображения результата позиции, все приложения на графике получают это событие. Однако во время программирования это неочевидно; это проявляется только при проверке кода на практике. На этом этапе мы снимаем прежнее ограничение. Даже на счете типа HEDGING все индикаторы позиции получат и выполнят одну и ту же команду: изменить способ отображения результата. Однако простое изменение режима отображения не обновляет данные, поэтому нам нужно сгенерировать новое событие. Это событие запрашивает у индикатора обновление информации. Поскольку каждый индикатор знает тикет позиции, которую он представляет, строка 268 полностью заменяет код из предыдущего фрагмента, где нам нужно было вызывать PositionsTotal и PositionGetTicket, чтобы охватить все позиции. Тем не менее, этот код еще можно улучшить. Пока что оставим это как есть.

Остается еще один вопрос: вызов PositionGetDouble. На первый взгляд кажется, что избавиться от него гораздо сложнее, но так ли это на самом деле? Давайте посмотрим, где и почему он используется. Так вы поймёте, как мы заменим этот вызов. Строка, содержащая этот вызов, приведена ниже.

270.                 case CHARTEVENT_OBJECT_CLICK:
271.                     sz0 = GetPositionsMouse().szObjNameClick;
272.                     if (m_Info.bClick)
273.                     {
274.                         if (sz0 == def_NameBtnMove)
275.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
276.                         if (sz0 == def_NameBtnClose)
277.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), m_Info.szSymbol);
278.                         if (sz0 == def_NameObjLabel)
279.                             EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL);
280.                     }
281.                     m_Info.bClick = false;
282.                     break;

Фрагмент класса C_ElementsTrade

Обратите внимание на строку 277. Здесь мы используем функцию PositionGetDouble для получения значений тейк-профита или стоп-лосса позиции. Это позволяет нам закрыть или удалить уровень ограничения, по которому был выполнен щелчок. То есть, когда мы щёлкаем, чтобы закрыть тейк-профит, нам нужно знать значение стоп-лосса. Точно так же, когда мы закрываем стоп-лосс, мы должны знать значение тейк-профита. Таким образом, между этими значениями есть перекрёстная зависимость. Решение этого вопроса представляет интерес, поскольку каждый сегмент будет связан с текущим значением. Возможно, то, что я сейчас сделаю, покажется вам немного непонятным, но вы скоро поймёте принцип работы. Чтобы убрать вызов PositionGetDouble, мы изменим класс C_ElementsTrade, как показано в следующем фрагменте.

026. //+------------------------------------------------------------------+
027. class C_ElementsTrade : private C_Mouse
028. {
029.     private    :
030. //+------------------------------------------------------------------+
031.         struct stInfos
032.         {
033.             struct st_01
034.             {
035.                 short   Width,
036.                         Height,
037.                         digits;
038.             }Text;
039.             ulong       ticket;
040.             string      szPrefixName,
041.                         szDescr,
042.                         szSymbol;
043.             EnumEvents  ev;
044.             double      price,
045.                         open,
046.                         volume,
047.                         var,
048.                         tickSize,
049.                         tpsl;
050.             bool        bClick,
051.                         bIsBuy;
052.             char        weight;
053.             color       _color;
054.             int         sizeText;
055.             enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;
056.         }m_Info;
057. //+------------------------------------------------------------------+

                                   .
                                   .
                                   .
                                   
190. //+------------------------------------------------------------------+
191. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0, const double special = -1)
192.         {
193.             m_Info.sizeText = 0;
194.             RemoveAllsObjects();
195.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
196.             m_Info.var = (var > 0 ? var : m_Info.var);
197.             m_Info.tpsl = (special >= 0 ? special : m_Info.tpsl);

                                   .
                                   .
                                   .
                                   
244. //+------------------------------------------------------------------+
245.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
246.         {

                                   .
                                   .
                                   .
                                   
272.                 case CHARTEVENT_OBJECT_CLICK:
273.                     sz0 = GetPositionsMouse().szObjNameClick;
274.                     if (m_Info.bClick)
275.                     {
276.                         if (sz0 == def_NameBtnMove)
277.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
278.                         if (sz0 == def_NameBtnClose)
279.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, m_Info.tpsl, m_Info.szSymbol);
280.                         if (sz0 == def_NameObjLabel)
281.                             EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL);
282.                     }
283.                     m_Info.bClick = false;
284.                     break;

Фрагмент класса C_ElementsTrade

Обратите внимание, что в строке 49 мы объявляем новую переменную. С этого начинается наше решение. Затем, в строке 191, мы изменяем объявление процедуры UpdatePrice, чтобы указать, какое значение получит переменная, объявленная в строке 49. А теперь внимание. В строке 197 мы проверяем, больше ли значение или равно нулю. Почему? Потому что, если тейк-профит или стоп-лосс не заданы, полученное значение будет равно нулю. Таким образом, мы можем правильно установить соответствующее значение. В процедуре DispatchMessage изменение видно в строке 279.

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

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

084. //+------------------------------------------------------------------+
085. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
086. {
087.     double volume;
088.     
089.     if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
090.     if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
091.     if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam);
092.     switch (id)
093.     {
094.         case CHARTEVENT_CUSTOM + evUpdate_Position:
095.             if (lparam != m_Infos.ticket) break;
096.             if (!PositionSelectByTicket(m_Infos.ticket))
097.             {
098.                 ChartIndicatorDelete(0, 0, m_Infos.szShortName);
099.                 return;
100.             };
101.             if (Open == NULL) Open = new C_ElementsTrade(
102.                                                         m_Infos.ticket,
103.                                                         m_Infos.szSymbol,
104.                                                         evMsgClosePositionEA, 
105.                                                         clrRoyalBlue, 
106.                                                         m_Infos.digits, 
107.                                                         m_Infos.tickSize, 
108.                                                         StringFormat("%I64u : Position opening price.", m_Infos.ticket), 
109.                                                         m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
110.                                                         );
111.             if (Take == NULL) Take = new C_ElementsTrade(
112.                                                         m_Infos.ticket,
113.                                                         m_Infos.szSymbol,
114.                                                         evMsgCloseTakeProfit, 
115.                                                         clrForestGreen, 
116.                                                         m_Infos.digits, 
117.                                                         m_Infos.tickSize, 
118.                                                         StringFormat("%I64u : Take Profit price.", m_Infos.ticket),
119.                                                         m_Infos.bIsBuy
120.                                                         );
121.             if (Stop == NULL) Stop = new C_ElementsTrade(
122.                                                         m_Infos.ticket, 
123.                                                         m_Infos.szSymbol,
124.                                                         evMsgCloseStopLoss, 
125.                                                         clrFireBrick, 
126.                                                         m_Infos.digits, 
127.                                                         m_Infos.tickSize, 
128.                                                         StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), 
129.                                                         m_Infos.bIsBuy
130.                                                         );
131.             volume = PositionGetDouble(POSITION_VOLUME);
132.             (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var);
133.             (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var, PositionGetDouble(POSITION_SL));
134.             (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var, PositionGetDouble(POSITION_TP));
135.             ProfitNow();
136.             break;
137.     }
138.     ChartRedraw();
139. };
140. //+------------------------------------------------------------------+

Фрагмент кода индикатора позиции

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

Обратите внимание: хотя мы удалили из класса C_ElementsTrade вызовы, предназначенные для получения данных о позиции, теперь эти же вызовы находятся в основном коде. Разница в том, что теперь мы лучше контролируем, как они будут использоваться. Благодаря этому нам удастся адаптировать этот же индикатор для использования с сервисом репликации/моделирования.

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

30. //+------------------------------------------------------------------+
31. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL;
32. //+------------------------------------------------------------------+
33. bool CheckCatch(ulong ticket)
34. {
35.     double vv;
36.     
37.     ZeroMemory(m_Infos);
38.     m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket);
39.     if (!PositionSelectByTicket(m_Infos.ticket)) return false;
40.     if (ChartWindowFind(0, m_Infos.szShortName) >= 0)
41.     {
42.         m_Infos.ticket = 0;
43.         return false;
44.     }
45.     m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL);
46.     m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
47.     m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
48.     vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
49.     m_Infos.var = m_Infos.tickSize / vv;
50.     IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
51.     EventChartCustom(0, evUpdate_Position, ticket, 0, "");
52.             
53.     return true;
54. }
55. //+------------------------------------------------------------------+

Фрагмент кода индикатора позиции

Да, мы по-прежнему работаем с основным кодом индикатора. Обратите внимание на строку 40. Даже в коде из предыдущей статьи там использовался вызов ObjectFind. Не спрашивайте меня почему, потому что я не смогу объяснить, почему использовал именно этот вызов. Правильный вызов — именно тот, который вы сейчас видите. Какова цель строки 40? Не допустить, чтобы трейдер или пользователь пытался добавить на график два индикатора, когда достаточно одного. Если попытаться это сделать, вызов ChartWindowFind в строке 40 проверит, существует ли уже на графике индикатор с тем же кратким именем. В этом случае индикатор, который пользователь пытается добавить, будет немедленно удалён. Таким образом, на графике всегда будет только один индикатор, связанный с каждой открытой позицией. Важно учитывать, что на счёте типа HEDGING может быть более одного индикатора позиции, но каждый из них будет привязан к отдельной позиции. Никогда не будет двух индикаторов, связанных с одной и той же позицией.

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


Индикация корректности тейк-профита и стоп-лосса

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

Сейчас я планирую реализовать это в индикаторе позиции. Таким образом, когда вы будете его использовать, вы заметите, что возникла какая-то проблема, и сможете принять необходимые меры. Это несложная задача; нужно лишь немного изменить существующий код. Однако я сделаю это несколько иначе, чем на вкладке «Торговля» в окне сообщений MetaTrader 5. Моя главная цель — предупредить трейдера или пользователя, если стоп-лосс или тейк-профит находятся на некорректном уровне. То есть когда они расположены неправильно и не образуют канал. Если объяснять это только словами, может показаться, что понять это немного сложно, но вскоре цель станет ясна. Чтобы сделать то, что нам нужно, потребуется добавить несколько элементов в класс C_ElementsTrade. В следующем фрагменте показано, где были внесены изменения.

026. //+------------------------------------------------------------------+
027. class C_ElementsTrade : private C_Mouse
028. {
029.     private    :
030. //+------------------------------------------------------------------+
031.         struct stInfos
032.         {
033.             struct st_01
034.             {
035.                 short   Width,
036.                         Height,
037.                         digits;
038.             }Text;
039.             ulong       ticket;
040.             string      szPrefixName,
041.                         szDescr,
042.                         szSymbol;
043.             EnumEvents  ev;
044.             double      price,
045.                         open,
046.                         volume,
047.                         var,
048.                         tickSize,
049.                         tpsl,
050.                         limit;
051.             bool        bClick,
052.                         bIsBuy;
053.             char        weight;
054.             color       _color;
055.             int         sizeText;
056.             enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;
057.         }m_Info;
058. //+------------------------------------------------------------------+

                                   .
                                   .
                                   .
                                   
164. //+------------------------------------------------------------------+
165. inline void ChartChange(void)
166.         {
167.             UpdateViewPort(MathAbs(m_Info.price));
168.             m_Info.limit = (m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
169.             if (m_Info.ev != evMsgClosePositionEA)
170.                 ViewValue(m_Info.limit);
171.         }
172. //+------------------------------------------------------------------+
                   
                                   .
                                   .
                                   .
218. //+------------------------------------------------------------------+
219.         void ViewValue(const double profit, const bool Enabled = true)
220.         {
221.             string szTxt;
222.             color  _cor;
223.             static double memSL, memTP;
224.             
225.             if (Enabled)
226.             {
227.                 switch (m_Info.ViewMode)
228.                 {
229.                     case stInfos::eValue:
230.                         szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit));
231.                         break;
232.                     case stInfos::eFinance:
233.                         szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume);
234.                         break;
235.                     case stInfos::eTicks:
236.                         szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize));
237.                         break;
238.                     case stInfos::ePercentage:
239.                         szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2));
240.                         break;
241.                 }
242.                 ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
243.                 if (StringLen(szTxt) != m_Info.sizeText)
244.                 {
245.                     AdjustDinamic(def_NameObjLabel, szTxt);
246.                     m_Info.sizeText = StringLen(szTxt);
247.                 }
248.             }
249.             _cor = (m_Info.limit >= 0 ? clrPaleGreen : clrCoral);
250.             switch (m_Info.ev)
251.             {
252.                 case evMsgCloseTakeProfit:
253.                     memTP = (Enabled ? memTP : profit);
254.                     _cor = (m_Info.limit < memTP ? clrYellow : _cor);
255.                     break;
256.                 case evMsgCloseStopLoss:
257.                     memSL = (Enabled ? memSL : profit);
258.                     _cor = (m_Info.limit > memSL ? clrYellow : _cor);
259.                     break;
260.                 case evMsgClosePositionEA:
261.                     _cor = (profit >= 0 ? clrPaleGreen : clrCoral);
262.                     break;
263.             }
264.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, _cor);
265.         }
266. //+------------------------------------------------------------------+
267.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
268.         {
269.             string sz0;
270.             long _lparam = lparam;
271.             double _dparam = dparam;
272.             
273.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
274.             switch (id)
275.             {

                                   .
                                   .
                                   .
                                   
307.                 case CHARTEVENT_MOUSE_MOVE:
308.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
309.                     if (m_Info.weight > 1)
310.                     {
311.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
312.                         ViewValue(m_Info.limit = (m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam));
313.                         if (m_Info.bClick)
314.                         {
315.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
316.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, m_Info.szSymbol);
317.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
318.                         }
319.                     }
320.                     break;

Фрагмент C_ElementsTrade

Я знаю, что это может показаться запутанным, но всё начинается с новой переменной, объявленной в строке 50. Этой переменной присваивается значение в двух местах. Первое — в строке 168. Принцип её работы довольно прост: значение будет зависеть от того, находимся ли мы на стороне покупки или продажи, а также от сегмента, используемого классом C_ElementsTrade. Таким образом, у нас будет одно значение для стоп-лосса и другое — для тейк-профита. Пока что оставим в стороне процедуру ViewValue, расположенную в строке 219, и рассмотрим второй момент, в котором переменной, объявленной в строке 50, присваивается значение. Эта вторая точка находится в строке 312. Обратите внимание на разницу: в строке 168 значение зависит от позиции, зарегистрированной на торговом сервере; в строке 312 — от движения мыши. Это связано с тем, что в этот момент мы будем изменять позицию, а именно её уровень тейк-профита или стоп-лосса.

Прежде чем анализировать процедуру ViewValue из строки 219, необходимо рассмотреть основной код индикатора. Я покажу только измененный фрагмент, который приведен ниже.

55. //+------------------------------------------------------------------+
56. inline void ProfitNow(void)
57. {
58.     double ask, bid, value;
59.     
60.     ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK);
61.     bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID);    
62.     if (Open != NULL)
63.     {
64.         (*Open).ViewValue(value = (m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask));
65.         (*Take).ViewValue(value, false);
66.         (*Stop).ViewValue(value, false);
67.     }
68. }
69. //+------------------------------------------------------------------+ 

Фрагмент кода индикатора позиции

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

Начнём со строки 223. Там мы видим две статические переменные. Принцип почти тот же, что мы использовали в процедуре UpdateViewPort. Однако, поскольку процедура ViewValue будет использоваться для двух сегментов — тейк-профита и стоп-лосса, — нам нужны две статические переменные. Мы могли бы объявить ещё одну переменную рядом с той, которую создали в строке 50. Однако зачастую давать имена переменным — задача непростая. На самом деле проблема не в выборе имени, а в том, чтобы не использовать переменную не в том месте. Но это лишь мелочь. После объявления статических переменных обратите внимание, что в строке 225 мы проверяем второй аргумент процедуры. Помните, что значение false он принимает только в двух местах, показанных в основном коде. В любом другом случае его значение будет true. Если это значение равно true, мы выполняем ту же работу, что и раньше. Если это значение равно false, мы не выполняем никаких действий. Однако настоящая работа начинается со строки 249. Именно в этот момент мы начинаем определять, находится ли цена внутри канала, образованного линиями стоп-лосса и тейк-профита. Когда цена выходит за пределы этого канала, и начинается всё самое интересное.

Давайте посмотрим, как это работает. В строке 249 мы используем предельное значение, чтобы определить, должен ли цвет фона быть зелёным или красным, независимо от того, какой сегмент выполняет код. В строке 250 мы выбираем между сегментами, чтобы правильно определить цвета. Возможно, вы сейчас задаётесь вопросом, зачем мы используем строку 249 для их установки, если потом всё равно переопределяем их с помощью этого выбора. Ответ находится в строках 254 и 258. Без строки 249 нам пришлось бы в обоих случаях повторять один и тот же код, используемый для присваивания переменной _cor. Давайте теперь посмотрим, что происходит в строках с 252 по 259, поскольку строка 260 довольно проста.

Я объясню случай evMsgCloseTakeProfit, хотя те же рассуждения применимы и к evMsgCloseStopLoss. В строках 253 и 257 мы выполняем одну и ту же проверку: проверяем, получаем ли значение из строк 65 и 66 основного кода. В этом случае мы сохраним его в соответствующей статической переменной. В противном случае мы повторно используем сохранённое значение, чтобы применить его в тернарном операторе. Затем, также с помощью тернарных операторов, в строках 254 и 258 мы проверяем, находится ли цена символа в пределах канала. Обратите внимание, что эта цена включает спред. По этой причине, даже если визуально может показаться, что всё в порядке, на деле это может быть не так. Если цена находится вне соответствующего канала, фон станет жёлтым. Если цена находится в подходящей зоне, фон может отображаться зелёным или красным, в зависимости от ситуации. Это можно увидеть в предыдущей анимации. Когда мы пытаемся установить стоп-лосс в неправильное положение, фон становится жёлтым, как только его линия входит в недопустимую зону.

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


Заключительные замечания

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

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

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

Во вложении будут доступны необходимые файлы и приложения для локального тестирования системы, если не знаете, как использовать информацию из статей для создания исходных кодов. На этом пока прощаюсь. Увидимся в следующей статье из этой серии, посвященной созданию сервиса репликации/моделирования. До следующего раза!


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

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Возможности Мастера MQL5, которые вам нужно знать (Часть 94): Использование выборки из резервуара и линейной регрессии в пользовательском классе трейлинг-стопа Возможности Мастера MQL5, которые вам нужно знать (Часть 94): Использование выборки из резервуара и линейной регрессии в пользовательском классе трейлинг-стопа
В этой статье мы переходим к пользовательской реализации класса для Мастера MQL5, посвященной трейлинг-стопам. Пользовательский класс CTrailingReservoirLinReg создается путем объединения алгоритма выборки из резервуара и сети линейной регрессии. Как и в других статьях серии, это решение можно тестировать с советниками, собранными Мастером MQL5 и настраиваемыми с различными сигналами входа и классами управления капиталом.
От начального до среднего уровня: Перегрузка операторов (I) От начального до среднего уровня: Перегрузка операторов (I)
В этой статье мы начнём изучать, как реализуется так называемая перегрузка операторов. Сначала мы рассмотрим, что лежит в основе этой реализации. Кроме того, мы убедимся, что всё не всегда так сложно, как кажется.
Разработка индикатора Market Memory Zones: Зоны вероятного возврата цены Разработка индикатора Market Memory Zones: Зоны вероятного возврата цены
В рамках данного обсуждения мы разработаем индикатор для выявления ценовых зон, сформированных в результате высокой рыночной активности, например: импульсных движений, структурных сдвигов и выбросов ликвидности. Эти зоны представляют собой области, где рынок оставил «память» из-за неисполненных ордеров или быстрого изменения цен. Отмечая эти области на графике, индикатор показывает, где цена со статистически более высокой вероятностью вернется и отреагирует в будущем.
Моделирование рынка: Position View (XVIII) Моделирование рынка: Position View (XVIII)
В этой статье я показал, в максимально доступной форме, как можно модифицировать и доработать код, способный решать определённые задачи, при этом минимально изменяя уже существующий код. Мы добавим индикатор объема и одновременно не позволим пользователю или трейдеру удалять объекты, создаваемые индикатором позиции.