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

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

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

Введение

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

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

Кроме того, возникает вопрос: как мы можем использовать ZOrder в своих интересах? Ответ прост: мы не можем. Это кажется абсурдным и совершенно бессмысленным. Тем не менее, как бы хорошо вы ни умели программировать, уважаемый читатель, вам не удастся превзойти то, что уже запрограммировано в MetaTrader 5 для управления ZOrder.

Тем не менее, нам необходимы средства для обработки создаваемых графических объектов. Предложение, представленное в предыдущей статье, отлично подходит для некоторых сценариев. В данном случае нам потребуется нечто более сложное, учитывая специфику рассматриваемой проблемы. Поэтому мы не будем пытаться заменить существующие в MetaTrader 5 механизмы управления ZOrder и, конечно же, проверять, какой объект находится на переднем плане или скрыт другим объектом. Мы сделаем нечто совершенно другое. Здесь я покажу, какие изменения необходимо внести в код, чтобы задействовать часть того, что MetaTrader 5 уже делает за нас. Иными словами, указывать, с каким объектом следует взаимодействовать, а с каким — нет, после получения клика.

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


Простейший код

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #define debug(A) Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
07. //+------------------------------------------------------------------+
08. int OnInit()
09. {
10.     string sz1 = "Object #01";
11.     
12.     ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0);
13.     ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 100);
14.     ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 100);
15.     
16.     sz1 = "Object #02";
17.     ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0);
18.     ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 200);
19.     ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 200);
20.     
21.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
22.         
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total,
27.                 const int prev_calculated,
28.                 const int begin,
29.                 const double &price[])
30. {
31.     return rates_total;
32. }
33. //+------------------------------------------------------------------+
34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
35. {
36.     switch(id)
37.     {
38.         case CHARTEVENT_OBJECT_CLICK:
39.             debug(sparam);
40.             break;
41.         case CHARTEVENT_MOUSE_MOVE:
42.             if (sparam != "0")
43.                 debug(sparam);
44.             break;
45.     }
46. };
47. //+------------------------------------------------------------------+
48. void OnDeinit(const int reason)
49. {
50.     ObjectsDeleteAll(0, "Object", -1, OBJ_BUTTON);
51. }
52. //+------------------------------------------------------------------+

Индикатор для тестирования

Данный код предельно прост. Всё, что он делает, - создает два объекта со стандартным ZOrder, то есть равным нулю, и выводит сообщение при возникновении события. Всё просто. Это может показаться глупым и даже наивным, но оно способно показать нам нечто чрезвычайно важное и полезное.

Когда мы запустим этот код на графике, мы получим результат, который можно увидеть в следующей анимации:

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

Ещё раз, обратите внимание на отображаемые сообщения. Теперь сравните их и скажите: в чем здесь закономерность? Если вы не заметили, попробуйте посмотреть на сообщения и код. И подумайте: какая здесь закономерность?

Если вам это не понятно, схема следующая: когда мы щелкаем по объекту на графике, MetaTrader 5 сначала запускает событие CHARTEVENT_MOUSE_MOVE, а затем событие CHARTEVENT_OBJECT_CLICK. Знать это важно. Почему? Зная это, мы можем использовать ту же самую закономерность в своих интересах для решения задачи, когда на графике много объектов. Таким образом, вопреки ожиданиям многих, мы не будем пытаться заменить существующий механизм в MetaTrader 5. Мы воспользуемся этим в своих интересах. Для этого данный шаблон, который вы можете протестировать на своей платформе, будет управляться нашими приложениями. Таким образом, когда мы будем проводить проверку с индикатором мыши, мы сможем игнорировать клики по объектам, которые имеют события в наших приложениях.

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

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

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


Обновление кода

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

Первым делом мы удалим уровни ZOrder. На самом деле, мы не будем их полностью исключать, а скорее мы их переопределим так, чтобы максимально использовать встроенную поддержку MetaTrader 5. Таким образом, MetaTrader 5 сможет легко сообщить нам, какой объект действительно получил событие клика. Таким образом мы изменим файл, который можно увидеть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.     #define macro_DEBUG_MODE(A) \
08.                     Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.     #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay          "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService       0xFED00000
16. #define def_IndicatorTimeFrame    (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame        4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.     double   dValue;
22.     long     _long;                                  // 1 Information
23.     datetime _datetime;                              // 1 Information
24.     uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.     ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.     uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents     {
30.             evTicTac,                        //Event of tic-tac
31.             evHideMouse,                     //Hide mouse price line
32.             evShowMouse,                     //Show mouse price line
33.             evHideBarTime,                   //Hide bar time
34.             evShowBarTime,                   //Show bar time
35.             evHideDailyVar,                  //Hide daily variation
36.             evShowDailyVar,                  //Show daily variation
37.             evHidePriceVar,                  //Hide instantaneous variation
38.             evShowPriceVar,                  //Show instantaneous variation
39.             evCtrlReplayInit,                //Initialize replay control
40.             evChartTradeBuy,                 //Market buy event
41.             evChartTradeSell,                //Market sales event 
42.             evChartTradeCloseAll,            //Event to close positions
43.             evChartTrade_At_EA,              //Event to communication
44.             evEA_At_ChartTrade,              //Event to communication
45.             evChatWriteSocket,               //Event to Mini Chat
46.             evChatReadSocket,                //Event To Mini Chat
47.             evUpdate_Position,               //Event to communication
48.             evMsgClosePositionEA,            //Event to communication
49.             evMsgCloseTakeProfit,            //Event to communication
50.             evMsgCloseStopLoss,              //Event to communication
51.             evMsgNewTakeProfit,              //Event to communication
52.             evMsgNewStopLoss                 //Event to communication
53.                         };
54. //+------------------------------------------------------------------+
55. enum EnumPriority {                          //Priority list on objects
56.             ePriorityNull = -1,
57.             ePriorityDefault = 0
58.                         };
59. //+------------------------------------------------------------------+

Defines.mqh

Обратите внимание, что теперь в EnumPriority меньше типов перечислений. Таким образом, мы получаем стандартный тип или по умолчанию тип, равный нулю. Также у нас есть ещё один — ePriorityNull. Данный тип следует применять ко всем объектам, которые не должны подвергаться никакому взаимодействию. Что ж, после внесения этого изменения нам придется заново скомпилировать весь наш код. Это помогает обеспечить его полную стабильность. При попытке компиляции индикатора Chart Trade возникают ошибки, связанные с классом C_ChartFloatingRAD. Их можно увидеть на следующем изображении:

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

164. //+------------------------------------------------------------------+
165.         template <typename T >
166.         void CreateObjectEditable(eObjectsIDE arg, T value)
167.             {
168.                 DeleteObjectEdit();
169.                 CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, ePriorityDefault);
170.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3);
171.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3);
172.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w);
173.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h);
174.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor);
175.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER);
176.                 ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1);
177.                 ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName);
178.                 ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value));
179.                 ChartRedraw();
180.             }
181. //+------------------------------------------------------------------+

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

После внесения изменений мы попытались заново скомпилировать индикатор Chart Trade. И его результат показан ниже:

Иными словами, успех. Мы можем переходить к следующему. Что касается индикатора мыши, здесь никаких изменений не будет, поскольку его ZOrder не претерпел никаких изменений. Таким образом, следующим шагом станет компиляция индикатора позиции. Но поскольку именно в коде класса C_ElementsTrade возникнут проблемы с ZOrder и этот же код претерпит дополнительные изменения, я пока не буду показывать модифицированный код. Перейдем к этапу использования той информации, которую мы получили в предыдущем разделе. То есть мы собираемся реализовать поддержку, которую нам предоставит MetaTrader 5. Таким образом, при клике на что-либо мы сможем точно узнать, по какому именно объекту был совершен клик. Однако при проведении исследования клик следует игнорировать.

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Macros.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. class C_Terminal
008. {
009. //+------------------------------------------------------------------+        
010.     public    :
011. //+------------------------------------------------------------------+        
012.         struct st_Mouse
013.         {
014.             struct st00
015.             {
016.                 short    X_Adjusted,
017.                          Y_Adjusted,
018.                          X_Graphics,
019.                          Y_Graphics;
020.                 double   Price;
021.                 datetime dt;
022.             }Position;
023.             uchar     ButtonStatus;
024.             bool      ExecStudy;
025.             string    szObjNameClick;
026.         };
027. //+------------------------------------------------------------------+
028.     protected:
029.         enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance};
030. //+------------------------------------------------------------------+
031.         struct st_Terminal
032.         {
033.             ENUM_SYMBOL_CHART_MODE   ChartMode;
034.             ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
035.             long    ID;
036.             string  szSymbol;
037.             int     Width,
038.                     Height,
039.                     nDigits,
040.                     SubWin,
041.                     HeightBar;
042.             double  PointPerTick,
043.                     ValuePerPoint,
044.                     VolumeMinimal,
045.                     AdjustToTrade;
046.         };
047. //+------------------------------------------------------------------+
048.         void CurrentSymbol(bool bUsingFull)
049.             {
050.                 MqlDateTime mdt1;
051.                 string sz0, sz1;
052.                 datetime dt = macroGetDate(TimeCurrent(mdt1));
053.                 enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
054.         
055.                 sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
056.                 for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
057.                 switch (eTS)
058.                 {
059.                     case DOL :
060.                     case WDO : sz1 = "FGHJKMNQUVXZ"; break;
061.                     case IND :
062.                     case WIN : sz1 = "GJMQVZ";       break;
063.                     default  : return;
064.                 }
065.                 sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS)));
066.                 for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
067.                     if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
068.             }
069. //+------------------------------------------------------------------+
070. inline void DecodeMousePosition(int xi, int yi)
071.             {
072.                 int w = 0;
073. 
074.                 xi = (xi > 0 ? xi : 0);
075.                 yi = (yi > 0 ? yi : 0);
076.                 ChartXYToTimePrice(m_Infos.ID, m_Mouse.Position.X_Graphics = (short)xi, m_Mouse.Position.Y_Graphics = (short)yi, w, m_Mouse.Position.dt, m_Mouse.Position.Price);
077.                 m_Mouse.Position.dt = AdjustTime(m_Mouse.Position.dt);
078.                 m_Mouse.Position.Price = AdjustPrice(m_Mouse.Position.Price);
079.                 ChartTimePriceToXY(m_Infos.ID, w, m_Mouse.Position.dt, m_Mouse.Position.Price, xi, yi);
080.                 yi -= (int)ChartGetInteger(m_Infos.ID, CHART_WINDOW_YDISTANCE, m_Infos.SubWin);
081.                 m_Mouse.Position.X_Adjusted = (short) xi;
082.                 m_Mouse.Position.Y_Adjusted = (short) yi;
083.             }
084. //+------------------------------------------------------------------+
085.     private    :
086.         st_Terminal m_Infos;
087.         st_Mouse    m_Mouse;
088.         struct mem
089.         {
090.             long  Show_Descr,
091.                   Show_Date;
092.             bool  AccountLock;
093.         }m_Mem;
094. //+------------------------------------------------------------------+
095. inline void ChartChange(void)
096.             {
097.                 int x, y, t;
098.                 
099.                 m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
100.                 m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
101.                 ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t);
102.                 ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y);
103.                 m_Infos.HeightBar = (int)(t - y) / 100;
104.             }
105. //+------------------------------------------------------------------+
106.     public    :
107. //+------------------------------------------------------------------+        
108.         C_Terminal(const long id = 0, const uchar sub = 0, const bool bFull = false)
109.             {
110.                 m_Infos.ID = (id == 0 ? ChartID() : id);
111.                 m_Mem.AccountLock = false;
112.                 m_Infos.SubWin = (int) sub;
113.                 CurrentSymbol(bFull);
114.                 ZeroMemory(m_Mouse);
115.                 m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
116.                 m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
117.                 ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
118.                 ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
119.                 ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
120.                 ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
121.                 m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
122.                 m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
123.                 m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
124.                 m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
125.                 m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
126.                 m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
127.                 m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
128.                 m_Infos.ChartMode    = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
129.                 if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
130.                 ChartChange();
131.             }
132. //+------------------------------------------------------------------+
133.         ~C_Terminal()
134.             {
135.                 ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
136.                 ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
137.                 ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false);
138.                 ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false);
139.             }
140. //+------------------------------------------------------------------+
141. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
142.             {
143.                 if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
144.                 m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
145.             }
146. //+------------------------------------------------------------------+
147. inline const st_Terminal GetInfoTerminal(void) const
148.             {
149.                 return m_Infos;
150.             }
151. //+------------------------------------------------------------------+
152. inline const st_Mouse GetPositionsMouse(void) const
153.             {
154.                 return m_Mouse;
155.             }
156. //+------------------------------------------------------------------+
157. const double AdjustPrice(const double arg) const
158.             {
159.                 return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits);
160.             }
161. //+------------------------------------------------------------------+
162. inline datetime AdjustTime(const datetime arg)
163.             {
164.                 int nSeconds= PeriodSeconds();
165.                 datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
166.                 
167.                 return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
168.             }
169. //+------------------------------------------------------------------+
170. inline double FinanceToPoints(const double Finance, const uint Leverage)
171.             {
172.                 double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
173.                 
174.                 return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
175.             };
176. //+------------------------------------------------------------------+
177.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
178.             {
179.                 static string st_str = "";
180.                 
181.                 switch (id)
182.                 {
183.                     case CHARTEVENT_CHART_CHANGE:
184.                         m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
185.                         m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
186.                         ChartChange();
187.                         break;
188.                     case CHARTEVENT_MOUSE_MOVE:
189.                         DecodeMousePosition((int)lparam, (int)dparam);
190.                         break;
191.                     case CHARTEVENT_OBJECT_CLICK:
192.                         m_Mouse.szObjNameClick = sparam;
193.                         if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
194.                         if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
195.                             ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
196.                         break;
197.                     case CHARTEVENT_OBJECT_CREATE:
198.                         if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
199.                         st_str = sparam;
200.                         break;
201.                 }
202.             }
203. //+------------------------------------------------------------------+
204. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const EnumPriority zOrder = ePriorityNull) const
205.             {
206.                 ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
207.                 ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0);
208.                 ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
209.                 ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
210.                 ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
211.                 ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
212.                 ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
213.                 ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
214.                 ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
215.             }
216. //+------------------------------------------------------------------+
217.         bool IndicatorCheckPass(const string szShortName)
218.             {
219.                 string szTmp = szShortName + "_TMP";
220.                 
221.                 IndicatorSetString(INDICATOR_SHORTNAME, szTmp);
222.                 m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szShortName)) < 0 ? 0 : m_Infos.SubWin);
223.                 if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE)
224.                 {
225.                     ChartIndicatorDelete(m_Infos.ID, 0, szTmp);
226.                     Print("Only one instance is allowed...");
227.                     SetUserError(C_Terminal::ERR_NoMoreInstance);
228.                     
229.                     return false;
230.                 }
231.                 IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
232.     
233.                 return true;
234.             }
235. //+------------------------------------------------------------------+
236. };

C_Terminal.mqh

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

Шутки в сторону. На самом деле, решение заключается в том, чтобы использовать MetaTrader 5 в своих интересах. Но для этого нам придётся прибегнуть к некой уловке. Этот приём можно реализовать здесь, в классе C_Terminal, или в том месте, где будет фактически использоваться реализованный здесь код. Возможно, вы подумаете: "разве реализация не должна происходить в классе C_Mouse, раз он отвечает за передачу событий клика в наши приложения?" Да, действительно, но если мы попытаемся это сделать, у нас возникнет небольшая проблема. Посмотрите ещё раз на анимацию. Обратите внимание, что событие CHARTEVENT_MOUSE_MOVE предшествует событию CHARTEVENT_OBJECT_CLICK. И когда мы анализируем, был ли клик по объекту во время анализа, у нас будет некоторая потеря синхронизации событий или, лучше сказать, когда вы кликнете по объекту, класс C_Mouse всё ещё будет указывать на объект, который получил предыдущий клик. Это может показаться странным. Но если мы посмотрим на класс C_ElementsTrade, всё станет яснее.

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


Обновление индикатора позиции

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

//+------------------------------------------------------------------+
01. #property copyright "Daniel Jose"
02. #property icon "/Images/Market Replay/Icons/Positions.ico"
03. #property description "Indicator for tracking an open position on the server."
04. #property description "This should preferably be used together with an Expert Advisor."
05. #property description "For more details see the same article."
06. #property version   "1.122"
07. #property link "https://www.mql5.com/pt/articles/13274"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #define def_ShortName "Position View"
12. //+------------------------------------------------------------------+
13. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
14. #include <Market Replay\Defines.mqh>
15. //+------------------------------------------------------------------+
16. input ulong user00 = 0;        //For Expert Advisor use
17. //+------------------------------------------------------------------+
18. C_IndicatorPosition *Positions = NULL;
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
23.     Positions = new C_IndicatorPosition();
24.     if (!Positions.CheckCatch(user00))
25.     {
26.         ChartIndicatorDelete(0, 0, def_ShortName);
27.         return INIT_FAILED;
28.     }
29. 
30.     return INIT_SUCCEEDED;
31. }
32. //+------------------------------------------------------------------+
33. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
34. {
35.     return rates_total;
36. }
37. //+------------------------------------------------------------------+
38. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
39. {
40.     (*Positions).DispatchMessage(id, lparam, dparam, sparam);
41. };
42. //+------------------------------------------------------------------+
43. void OnDeinit(const int reason)
44. {
45.     delete Positions;
46. }
47. //+------------------------------------------------------------------+

Индикатор позиции:

В связи с этим код класса C_IndicatorPosition немного изменился. Полный текст нового кода можно увидеть ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_ElementsTrade.mqh"
05. //+------------------------------------------------------------------+
06. class C_IndicatorPosition
07. {
08.     private    :
09.         struct st00
10.         {
11.             ulong   ticket;
12.             string  szShortName;
13.         }m_Infos;
14.         C_ElementsTrade *Open, *Stop, *Take;
15. //+------------------------------------------------------------------+
16.     public    :
17. //+------------------------------------------------------------------+
18.         C_IndicatorPosition()
19.         {
20.             ZeroMemory(m_Infos);
21.             Open = Take = Stop = NULL;
22.         }
23. //+------------------------------------------------------------------+
24.         ~C_IndicatorPosition()
25.         {
26.             delete Open;
27.             delete Take;
28.             delete Stop;
29.         }
30. //+------------------------------------------------------------------+
31.         bool CheckCatch(ulong ticket)
32.         {
33.             m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket);
34.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
35.             if (ObjectFind(0, m_Infos.szShortName) >= 0)
36.             {
37.                 m_Infos.ticket = 0;
38.                 return false;
39.             }
40.             IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
41.             EventChartCustom(0, evUpdate_Position, ticket, 0, "");
42.             
43.             return true;
44.         }
45. //+------------------------------------------------------------------+
46.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
47.         {
48.             double value;
49.             
50.             if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
51.             if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
52.             if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam);
53.             switch (id)
54.             {
55.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
56.                     if (lparam != m_Infos.ticket) return;
57.                     if (!PositionSelectByTicket(m_Infos.ticket))
58.                     {
59.                         ChartIndicatorDelete(0, 0, m_Infos.szShortName);
60.                         return;
61.                     };
62.                     if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket));
63.                     if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket));
64.                     if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket));
65.                     (*Open).UpdatePrice(PositionGetDouble(POSITION_PRICE_OPEN));
66.                     if ((value = PositionGetDouble(POSITION_TP)) > 0) (*Take).UpdatePrice(value); else
67.                     {
68.                         delete Take;
69.                         Take = NULL;
70.                     }
71.                     if ((value = PositionGetDouble(POSITION_SL)) > 0) (*Stop).UpdatePrice(value);    else
72.                     {
73.                         delete Stop;
74.                         Stop = NULL;
75.                     }
76.                     break;
77.             }
78.             ChartRedraw();
79.         }
80. //+------------------------------------------------------------------+
81. };
82. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine     m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose  m_Info.szPrefixName + "#CLOSE"
006. //+------------------------------------------------------------------+
007. #define def_PathBtns "Images\\Market Replay\\Orders\\"
008. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
009. #resource "\\" + def_Btn_Close;
010. //+------------------------------------------------------------------+
011. #include "..\Auxiliar\C_Mouse.mqh"
012. //+------------------------------------------------------------------+
013. class C_ElementsTrade : private C_Mouse
014. {
015.     private    :
016. //+------------------------------------------------------------------+
017.         struct st00
018.         {
019.             ulong      ticket;
020.             string     szPrefixName;
021.             EnumEvents ev;
022.             double     price;
023.             bool       bClick;
024.         }m_Info;
025. //+------------------------------------------------------------------+
026.         void UpdateViewPort(void)
027.         {
028.             int x, y;
029.             
030.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);
031.             ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price);
032.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, 130);
033.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
034.         }
035. //+------------------------------------------------------------------+
036.     public    :
037. //+------------------------------------------------------------------+
038.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
039.             :C_Mouse(0, "")
040.         {
041.             string szObj;
042.             
043.             ZeroMemory(m_Info);
044.             m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
045.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio);
046.             ObjectSetInteger(0, szObj, OBJPROP_WIDTH, 2);
047.             ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr);
048.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr);
049.             ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull);
050.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
051.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
052.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
053.         }
054. //+------------------------------------------------------------------+
055.         ~C_ElementsTrade()
056.         {
057.             if (m_Info.szPrefixName != "")
058.                 ObjectsDeleteAll(0, m_Info.szPrefixName);
059.         }
060. //+------------------------------------------------------------------+
061.         inline void UpdatePrice(const double price)
062.         {
063.             m_Info.price = price;
064.             UpdateViewPort();
065.         }
066. //+------------------------------------------------------------------+
067.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
068.         {            
069.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
070.             switch (id)
071.             {
072.                 case CHARTEVENT_OBJECT_CLICK:
073.                     if ((m_Info.bClick) && (GetPositionsMouse().szObjNameClick == def_NameBtnClose)) switch (m_Info.ev)
074.                     {
075.                         case evMsgClosePositionEA:
076.                             EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
077.                             break;
078.                         case evMsgCloseTakeProfit:
079.                             EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
080.                             break;
081.                         case evMsgCloseStopLoss:
082.                             EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
083.                         break;
084.                     }
085.                     m_Info.bClick = false;
086.                     break;
087.                 case CHARTEVENT_MOUSE_MOVE:
088.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
089.                     break;
090.                 case CHARTEVENT_CHART_CHANGE:
091.                     UpdateViewPort();
092.                     break;
093.             }
094.         }
095. //+------------------------------------------------------------------+
096. };
097. //+------------------------------------------------------------------+
098. #undef def_Btn_Close
099. #undef def_PathBtns
100. //+------------------------------------------------------------------+
101. #undef def_NameBtnClose
102. #undef def_NameHLine
103. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

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

Обратите внимание, что в строке 67, где начинается процедура DispatchMessage, мы не внесли никаких существенных изменений. Мы просто адаптировали существующий код. Прошу заметить, что в обработчике события CHARTEVENT_MOUSE_MOVE, в строке 87, мы проверяем, наряду с индикатором мыши, является ли щелчок действительным или нет. Мы делаем это довольно простым и понятным способом. Таким образом, переменная из строки 23 получит значение true при действительном щелчке мыши или false, если индикатор мыши выполнит какое-либо анализ. Именно эту часть будет обрабатывать наше приложение. Область, где MetaTrader 5 может нам помочь, можно увидеть в событии CHARTEVENT_OBJECT_CLICK, из строки 72.

Обратите внимание на одну деталь в этом коде, а именно в событии CHARTEVENT_OBJECT_CLICK. Это в точности то же самое, что и в предыдущей статье. Однако это происходило в событии CHARTEVENT_MOUSE_MOVE. Но возникнут ли у нас здесь проблемы? На самом деле нет, потому что перед событием клика произойдет событие перемещения. И когда произойдет клик, мы проверим, является ли он действительным или нет. Будет выполнено только одно действие, как было показано в предыдущей статье, только при том условии, что мы имеем дело с валидным кликом, а имя объекта соответствует имени, обрабатываемому в данный момент. То есть, объект, представляющий собой кнопку закрытия. Всё остальное уже подробно объяснено, поэтому никаких дополнительных пояснений не требуется.

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


Подготовка к следующим шагам

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

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

Кроме того, существует ещё одна проблема, которая, пусть пока и не влияет на нашу работу, но создаст огромные трудности в будущем, если мы не реализуем архитектуру более универсальным способом. Речь идет об отложенных ордерах. В них будет использоваться система, очень похожая на ту, которую мы собираемся разработать сейчас. Следовательно, внутреннего переключателя для внесения соответствующих изменений будет недостаточно. Однако, к счастью, у нас есть столь же простое решение, которое полностью охватит все возможные сценарии. Это включает в себя передачу сообщений между индикаторами. Можно себе представить, что это чрезвычайно сложная и трудная для реализации задача. Но так ли это будет на самом деле? Прежде чем рассматривать необходимые изменения, давайте посмотрим, что изменилось в заголовочном файле Defines.mqh. Его можно увидеть ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.     #define macro_DEBUG_MODE(A) \
08.                     Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.     #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay          "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService       0xFED00000
16. #define def_IndicatorTimeFrame    (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame        4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.     double   dValue;
22.     long     _long;                                  // 1 Information
23.     datetime _datetime;                              // 1 Information
24.     uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.     ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.     uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents     {
30.             evTicTac,                        //Event of tic-tac
31.             evHideMouse,                     //Hide mouse price line
32.             evShowMouse,                     //Show mouse price line
33.             evHideBarTime,                   //Hide bar time
34.             evShowBarTime,                   //Show bar time
35.             evHideDailyVar,                  //Hide daily variation
36.             evShowDailyVar,                  //Show daily variation
37.             evHidePriceVar,                  //Hide instantaneous variation
38.             evShowPriceVar,                  //Show instantaneous variation
39.             evCtrlReplayInit,                //Initialize replay control
40.             evChartTradeBuy,                 //Market buy event
41.             evChartTradeSell,                //Market sales event 
42.             evChartTradeCloseAll,            //Event to close positions
43.             evChartTrade_At_EA,              //Event to communication
44.             evEA_At_ChartTrade,              //Event to communication
45.             evChatWriteSocket,               //Event to Mini Chat
46.             evChatReadSocket,                //Event To Mini Chat
47.             evUpdate_Position,               //Event to communication
48.             evMsgClosePositionEA,            //Event to communication
49.             evMsgCloseTakeProfit,            //Event to communication
50.             evMsgCloseStopLoss,              //Event to communication
51.             evMsgNewTakeProfit,              //Event to communication
52.             evMsgNewStopLoss,                //Event to communication
53.             evMsgSetFocus                    //Event to communication
54.                         };
55. //+------------------------------------------------------------------+
56. enum EnumPriority {                          //Priority list on objects
57.             ePriorityNull = -1,
58.             ePriorityDefault = 0
59.                         };
60. //+------------------------------------------------------------------+

Defines.mqh

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine      m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose   m_Info.szPrefixName + "#CLOSE"
006. //+------------------------------------------------------------------+
007. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 7 : 3));
008. //+------------------------------------------------------------------+
009. #define def_PathBtns "Images\\Market Replay\\Orders\\"
010. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
011. #resource "\\" + def_Btn_Close;
012. //+------------------------------------------------------------------+
013. #include "..\Auxiliar\C_Mouse.mqh"
014. //+------------------------------------------------------------------+
015. class C_ElementsTrade : private C_Mouse
016. {
017.     private    :
018. //+------------------------------------------------------------------+
019.         struct st00
020.         {
021.             ulong       ticket;
022.             string      szPrefixName;
023.             EnumEvents  ev;
024.             double      price;
025.             bool        bClick;
026.             char        weight;
027.         }m_Info;
028. //+------------------------------------------------------------------+
029.         void UpdateViewPort(void)
030.         {
031.             int x, y;
032.             
033.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);            
034.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 200 : 250));
035.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
036.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight / 2));
037.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
038.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
039.         }
040. //+------------------------------------------------------------------+
041.     public    :
042. //+------------------------------------------------------------------+
043.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
044.             :C_Mouse(0, "")
045.         {
046.             string szObj;
047.             
048.             ZeroMemory(m_Info);
049.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
050.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault));
051.             macro_LineInFocus(false);
052.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _color);
053.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
054.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
055.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
056.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr);
057.             ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull);
058.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
059.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
060.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
061.         }
062. //+------------------------------------------------------------------+
063.         ~C_ElementsTrade()
064.         {
065.             ObjectsDeleteAll(0, m_Info.szPrefixName);
066.         }
067. //+------------------------------------------------------------------+
068.         inline void UpdatePrice(const double price)
069.         {
070.             m_Info.price = price;
071.             UpdateViewPort();
072.         }
073. //+------------------------------------------------------------------+
074.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
075.         {
076.             string sz0;
077.             
078.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
079.             switch (id)
080.             {
081.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
082.                     macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev));
083.                     break;
084.                 case CHARTEVENT_OBJECT_CLICK:
085.                     sz0 = GetPositionsMouse().szObjNameClick;
086.                     if (m_Info.bClick) switch (m_Info.ev)
087.                     {
088.                         case evMsgClosePositionEA:
089.                             if (sz0 == def_NameBtnClose)
090.                                 EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
091.                             else if (sz0 == def_NameHLine)
092.                                 EventChartCustom(0, evMsgSetFocus, 0, 0, "");
093.                             break;
094.                         case evMsgCloseTakeProfit:
095.                             if (sz0 == def_NameBtnClose)
096.                                 EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
097.                             else if (sz0 == def_NameHLine)
098.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, "");
099.                             break;
100.                         case evMsgCloseStopLoss:
101.                             if (sz0 == def_NameBtnClose)
102.                                 EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
103.                             else if (sz0 == def_NameHLine)
104.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, "");
105.                             break;
106.                     }
107.                     m_Info.bClick = false;
108.                     break;
109.                 case CHARTEVENT_MOUSE_MOVE:
110.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
111.                     break;
112.                 case CHARTEVENT_CHART_CHANGE:
113.                     UpdateViewPort();
114.                     break;
115.             }
116.         }
117. //+------------------------------------------------------------------+
118. };
119. //+------------------------------------------------------------------+
120. #undef macro_LineInFocus
121. //+------------------------------------------------------------------+
122. #undef def_Btn_Close
123. #undef def_PathBtns
124. //+------------------------------------------------------------------+
125. #undef def_NameBtnClose
126. #undef def_NameHLine
127. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

А ожидаемый результат можно посмотреть в следующей анимации.

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

Прежде всего обратите внимание, что в строке 07 мы определили макрос для весьма очевидного выделения линии, которая получает фокус. В строке 26 я определил новую переменную, которая будет хранить значение толщины линии, определенной в этом классе. В строке 36 мы расположим созданную здесь линию по центру. Для правильного выполнения данной операции толщина линии должна быть задана нечетным значением. Если задать четное значение, например, два или четыре, линия будет немного смещена относительно идеальной позиции. Обратите внимание еще на один момент: в строке 34 мы определяем местоположение каждой из кнопок. Это предотвращает их чрезмерное сближение или перекрытие. Это затруднит правильный доступ к ним. Данные значения не являются окончательными, но они уже выполняют свою функцию.

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

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

Обратите внимание, что в строке 81 происходит обработка сообщения, созданного нами в файле Defines.mqh. Однако это сообщение существует здесь лишь потому, что мы обобщаем вещи до крайности. Прошу заметить, что обработка сообщений очень проста. Если класс C_ElementsTrade получает это сообщение, evMsgSetFocus, он проверяет, совпадает ли значение lparam с тикетом, отслеживаемым классом. Кроме того, параметр dparam должен быть одним из значений события. Если этого не произойдет, линия будет перенастроена на меньшую толщину. Если условие выполнено, толщина линии будет увеличена. Всё просто.

"Но подождите немного. Как класс поймет, когда линия станет толще или тоньше? Разве это не должно быть где-нибудь определено?" Да, уважаемый читатель. Фактическую задачу указания на это выполняют события, описанные в строках 92, 98 и 104. Каждое из них срабатывает только при наличии действительного щелчка мыши по индикатору. Обратите внимание, что условием, позволяющим запускать данные события, является именно имя объекта, на который был нажат щелчок. "Да, но почему именно так? Разве не было бы быстрее, проще и практичнее использовать макрос вместо запуска этих событий?" И снова вы правы, уважаемый читатель.

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


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

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

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

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

Файл Описание
Experts\Expert Advisor.mq5
Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study).
Indicators\Chart Trade.mq5 Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study)
Indicators\Market Replay.mq5 Создает элементы управления для взаимодействия со сервисом репликации/моделирования (для взаимодействия требуется Mouse Study).
Indicators\Mouse Study.mq5 Обеспечивает взаимодействие между графическими элементами управления и пользователем (необходимое как для работы системы репликации/моделирования, так и на реальном рынке).
Indicators\Order Indicator.mq5 Отвечает за отображение рыночных ордеров, обеспечение их взаимодействия и контроля.
Indicators\Position View.mq5 Отвечает за отображение рыночных позиций, обеспечивая взаимодействие и контроль.
Services\Market Replay.mq5 Создает и поддерживает сервис репликации/моделирования рынка (главный файл всей системы).

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/13274

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Кооперация бионических алгоритмов — Co-Operation of Biology Related Algorithms (COBRA) Кооперация бионических алгоритмов — Co-Operation of Biology Related Algorithms (COBRA)
В статье реализована кооперативная метаэвристика COBRA во фреймворке C_AO: шесть популяционных методов (PSO, WPS, FFA, CSA, BA, FSS) работают в одной популяции при фиксированном бюджете обращений к целевой функции и экспериментально выясняем, на чём на самом деле держится кооперация. В комплекте — расширяемый класс для сборки собственных кооперативных портфелей из любых алгоритмов серии.
Автоматизация торговых стратегий на MQL5 (Часть 36): Торговля по зонам спроса и предложения на основе модели ретеста и импульса Автоматизация торговых стратегий на MQL5 (Часть 36): Торговля по зонам спроса и предложения на основе модели ретеста и импульса
В этой статье мы создадим на MQL5 торговую систему на основе зон спроса и предложения. Она определяет такие зоны по диапазонам консолидации, подтверждает их импульсными движениями и открывает позиции на ретестах с подтверждением тренда и настраиваемыми параметрами риска. Система отображает зоны с помощью динамических подписей и цветов, а также поддерживает трейлинг-стопы для управления рисками.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Анализ CSV-данных (Часть 6): Нормализация результатов по нескольким брокерам и сверка CSV между платформами Анализ CSV-данных (Часть 6): Нормализация результатов по нескольким брокерам и сверка CSV между платформами
В статье представлен фреймворк нормализации CSV-данных от нескольких брокеров. Подключаемый файл MQL5 дополняет экспорт брокерскими метаданными. Модуль Python устраняет расхождения схем — различия в разрядности котирования и соглашениях о пунктах, вариантах названий символов, временных смещениях, моделях комиссий и валютах счёта и валютах, в которых выражена прибыль — и формирует единый канонический набор данных. Сравнительные визуализации распределений проскальзывания и результатов после учёта издержек позволяют надёжно сопоставлять результаты стратегии между разными брокерскими средами без незаметного искажения данных.