Русский Español Português
preview
Market Simulation: Position View (X)

Market Simulation: Position View (X)

MetaTrader 5Tester |
211 0
Daniel Jose
Daniel Jose

Introduction

Hello, everyone, and welcome to a new article in our series on how to create a replication/simulation system.

In the previous article Market Simulation: Position View (IX), we began exploring how the movement of the Take Profit and Stop Loss lines should be implemented. Since I want to explain to everyone—and make it as clear as possible—how and why changes are made to the code, in this article we will try to solve the problem presented in the previous article. The problem lies with ZOrder. If you are not sure what the ZOrder property means for objects, don't worry—just check out the previous articles in this series, where I explain the importance of setting this property correctly.

In addition, the question arises: how can we use ZOrder to our advantage? The answer is simple: we cannot. This seems absurd and completely pointless. Nevertheless, no matter how skilled a programmer you may be, dear reader, you will not be able to surpass what is already programmed in MetaTrader 5 for managing ZOrder.

Nevertheless, we need tools for handling the graphical objects we create. The approach presented in the previous article works very well for certain scenarios. In this case, we will need something more complex, given the specific nature of the problem at hand. Therefore, we will not attempt to replace the ZOrder management mechanisms that exist in MetaTrader 5, nor, of course, will we check which object is in the foreground or hidden by another object. We are going to do something completely different. Here, I'll show you what changes need to be made to the code in order to make use of some of what MetaTrader 5 already does for us. In other words, to determine which object should be interacted with and which should not after a click is received.

To help you understand what we are going to do, we first need to look at a fairly simple piece of code that is, nonetheless, extremely useful for understanding how to approach our problem.


The simplest code

To understand this, you need to use something you can actually understand. Many people tend to think that to solve a problem, we need to come up with a convoluted or extremely complex solution. But in practice, we should not do that. In practice, we need to create something as simple as possible. We need to use the application and understand what it is telling us. To demonstrate this, we'll use the code below:

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

Indicator for testing

This code is extremely simple. All it does is create two objects with the standard ZOrder value—that is, zero—and display a message when an event occurs. It is simple. This may seem silly or even naive, but it can show us something extremely important and useful.

When we run this code on the chart, we'll get the result shown in the following animation:

In this animation, the main focus is on the messages that appear. Forget about everything else and focus on the messages. Note that there is a pattern here. So what will happen now if we add an object that overlaps the buttons? What will happen? To answer this question, just take a look at the following animation:

Once again, pay attention to the displayed messages. Now compare them and tell me: what's the pattern here? If you have not noticed, try looking at the messages and the code. And think about it: what's the pattern here?

If this isn't clear, here is how it works: when we click on an object on the chart, MetaTrader 5 first triggers the CHARTEVENT_MOUSE_MOVE event, and then the CHARTEVENT_OBJECT_CLICK event. It is important to know this. Why? Knowing this, we can use the same pattern to our advantage to solve the problem that arises when there are many objects on a chart. Thus, contrary to what many might expect, we will not try to replace the existing mechanism in MetaTrader 5. We will use this to our advantage. For this purpose, this template, which you can test on your platform, will be controlled by our applications. Thus, when we perform a check using the mouse indicator, we will be able to ignore clicks on objects whose events are handled in our applications.

You might be thinking, "But how are we going to do this? We've already made quite a bit of progress on the code. Do we really have to go back to the beginning and completely abandon everything that has already been created?" No, dear reader. As I mentioned earlier, the purpose of this series of articles is not to show exactly how to develop a replication/simulation system, but rather to demonstrate how a programmer should approach the problems that arise. Many people believe that creating an application is a simple and hassle-free process. But those who truly immerse themselves in this world know that things are actually quite convoluted. We often have to solve problems of varying degrees of complexity.

This issue with ZOrder can be quite a challenge in many cases. You, dear reader, might simply think that at no point during the development process did I, as a programmer, encounter any problems or difficulties. This is because if I had simply fixed the code and published it without any errors, you might have been under the false impression that everything went perfectly, and that if you, dear reader, are having trouble writing code, it is only because—pardon the expression—you’re a complete amateur. But that is not the case. We all face challenges. The way we overcome these challenges allows us to learn and become professionals with varying levels of expertise.

So, let us think about the following, and I want you to try to follow the line of reasoning. How can we use the information we get from this simple program to our advantage so that we have to modify the code we've already designed and implemented as little as possible? This is exactly the kind of question that would eventually make many people give up trying to solve it, since it seems like an extremely difficult task. But, as I have already said, I want to show you, dear reader, that if you stop for a few moments and think about it, you will eventually realize what needs to be done.


Updating the Code

Before we continue, I hope you have really tried to come up with a solution to the problem. Because, as unbelievable as it may sound, it is actually simpler than many people might imagine.

First, we will remove the ZOrder levels. In fact, we will not eliminate them entirely; rather, we will redefine them to take full advantage of MetaTrader 5's built-in support. This way, MetaTrader 5 will be able to easily tell us which object actually received the click event. So we will modify the file shown below.

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

Note that EnumPriority now contains fewer enumeration values. Thus, we get a standard type, or a default type, with a value of zero. We also have another one: ePriorityNull. This type should be applied to all objects that should not participate in any interaction. Well, after making this change, we'll have to recompile all of our code. This helps ensure its complete stability. When you try to compile the Chart Trade indicator, errors related to the C_ChartFloatingRAD class occur. You can see them in the following image:

At this stage, many people start tearing their hair out, thinking, “What have we done?” Nothing will work anymore. But will that really be the case? If we go to the location where the first error is reported—that is, line 169 of the C_ChartFloatingRAD class—we will see the cause of the error. To fix this, simply replace the original code with the code shown in the following snippet:

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

Fragment of the C_ChartFloatingRAD.mqh file

After making the changes, we tried to recompile the Chart Trade indicator. And the result is shown below:

In other words, success. We can move on to the next one. As for the mouse indicator, there will be no changes here, since its ZOrder has not changed. Therefore, the next step will be to compile the position indicator. However, since issues with ZOrder will arise specifically in the code of the C_ElementsTrade class—and that same code will undergo further changes—I will not show the modified code just yet. Let's move on to the stage of using the information we obtained in the previous section. In other words, we are going to implement the support that MetaTrader 5 provides us. This way, when we click on something, we will be able to determine exactly which object was clicked. However, during this check, the click should be ignored.

This will require deep and radical changes. It's extremely difficult to even imagine this. Something only a great guru could have come up with and implemented. So pay attention to how incredibly complex and convoluted this solution is. You can see it in the following code:

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

The solution is implemented in the code shown above. Did you notice where? If you do not follow this series or study the materials I have provided, you definitely will not be able to find the solution. Actually, I deliberately included the complete code for the C_Terminal class so that you'd get completely confused and wouldn't understand exactly where the solution was implemented.

All joking aside. In fact, the solution is to use MetaTrader 5 to our advantage. But to do that, we will have to resort to a little trick. This technique can be implemented here, in the C_Terminal class, or wherever the code implemented here will actually be used. You might be thinking, "Shouldn't the implementation be in the C_Mouse class, since it's responsible for passing click events to our applications?" Yes, that is true, but if we try to do that, we will run into a small problem. Take another look at the animation. Note that the CHARTEVENT_MOUSE_MOVE event precedes the CHARTEVENT_OBJECT_CLICK event. And when we analyze whether a click occurred on an object, there will be some loss of event synchronization—or, to put it another way, when you click on an object, the C_Mouse class will still be pointing to the object that received the previous click. This might seem strange. But if we take a look at the C_ElementsTrade class, everything will become clearer.

So, once again, the solution was to add a new variable. It is located on line 25 and is used only on line 192. As mentioned earlier, we could place the solution within the same code that will use it. But since I want everything to be very well organized, we'll place it here. That is, in the C_Terminal class. Thus, we can now look at the code for the position indicator.


Updating the position indicator

After making changes to the other code snippets shown above, we can see what had to be changed in the position indicator. First, it was decided that the trader or user would no longer be able to change the colors used for the lines. They will be standardized within the indicator. As a result, the code in the main file has changed, and you can see it below.

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

Position indicator:

As a result, the code for the C_IndicatorPosition class has changed slightly. The full text of the new code is provided below:

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

Based on all this, it is time to finally take a look at the new code for the C_ElementsTrade class. It is shown below. Note that the code is significantly different from the one we looked at in the previous article. Nevertheless, it contains the same elements and works in exactly the same way.

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

Several of the previously included elements have been removed, which is clearly evident when comparing the two code listings. However, keep in mind that a new variable has appeared on line 23. It is used in the DispatchMessage procedure. Otherwise, nothing has changed. But let's take a look at how the DispatchMessage procedure will work now. This is because, from this point on, the procedure will rely on MetaTrader 5.

Note that on line 67, where the DispatchMessage procedure begins, we have not made any significant changes. We simply adapted the existing code. Note that in the CHARTEVENT_MOUSE_MOVE event handler, on line 87, we check—in addition to the mouse indicator—whether the click is valid or not. We do this in a fairly simple and straightforward way. Thus, the variable from line 23 will be set to true for a valid mouse click, or to false if the mouse indicator performs any kind of analysis. This is the part that our application will process. The part where MetaTrader 5 can help us can be seen in the CHARTEVENT_OBJECT_CLICK event, starting at line 72.

Note one detail in this code, specifically in the CHARTEVENT_OBJECT_CLICK event. This is exactly the same as in the previous article. However, this occurred in the CHARTEVENT_MOUSE_MOVE event. But will we run into any problems here? Actually, no, because a mouse-move event will occur before the click event. And when a click occurs, we will check whether it is valid or not. As shown in the previous article, only one action will be performed—but only if the click is valid and the object's name matches the name currently being processed. In other words, the object representing the close button. Everything else has already been explained in detail, so no further clarification is needed.

Before we conclude this article, however, we will make a small change to the system. This is because there isn't much point in having a horizontal line that spans the entire width of the chart, with a close button located at some point along it. We can improve this so that the line ends at the close button, which would make a little more sense. However, this will lay the groundwork for yet another modification that will be implemented in the future. But to get closer to the desired result, we will have to modify the class. Since I am not entirely sure how things will develop from here, don't get too attached to what we are going to explain, but do try to understand how it all works. This might come in handy for you in the future.


Preparing for the Next Steps

The next step we're going to take may seem somewhat absurd and even unnecessary. However, this is important in light of what we will be implementing next. So, what is the main point at this stage? The idea is to create a visual and sufficiently clear way to verify exactly which line we are controlling. And why is that important? The reason is that we can use a system where one click selects the line, and another click releases it. And without knowing exactly which line we are working with, it is very difficult to do this correctly. Therefore, we need a simple and effective way to provide this information.

Many people might think, “Since we have an open position, we can simply create an internal switching system.” And once again, I agree with you, dear reader. Implementing an internal switching system is the most sensible solution. However, let's look at this a bit more generally. Consider the following: if you are using a HEDGING account, the switching system will not be effective enough, since two or more position indicators may be present on the same chart at the same time. This makes implementing such switching quite complicated, given the possibility that you might click on the line of one position and then click on the line of another position. In other words, in this case, we will run into a problem.

In addition, there is another problem that, while it does not currently affect our work, will create enormous difficulties in the future if we do not implement the architecture in a more universal way. We are talking about pending orders. Pending orders will use a system very similar to the one we are about to develop now. Consequently, an internal switch will not be enough to make the corresponding changes. Fortunately, however, we have an equally simple solution that will fully cover all possible scenarios. This involves message passing between indicators. You might imagine that this is an extremely complex task that is difficult to implement. But will that really be the case? Before we look at the necessary changes, let's see what has changed in the Defines.mqh header file. It is shown below:

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

Note that a new event is defined on line 53. We will use this event to carry out the task we want to accomplish. Next, we can move on to the code for the C_ElementsTrade class to see the changes that were necessary to achieve the expected result. You can see the entire class code below.

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

You can see the expected result in the following animation.

I am showing this result before explaining the code so that you, dear reader, will notice what is going to be explained next. This mechanism may undergo some changes in the future. However, if at any point you need to do something like this, you should know that there are several ways to do it. But you need to understand why these methods work. With that said, I think you are probably curious to understand how we managed to implement the previous animation without creating a conventional switching mechanism. Let's examine, then, how and why this happens.

First of all, note that on line 07, we defined a macro to very clearly highlight the line that gains focus. On line 26, I defined a new variable that will store the line thickness value defined in this class. On line 36, we will center the line created here. To perform this operation correctly, the line thickness must be set to an odd number. If you specify an even number, such as two or four, the line will be slightly offset from its ideal position. Note one more thing: on line 34, we specify the position of each button. This prevents them from getting too close together or overlapping. This will make it difficult to access them properly. These values are not final, but they already serve their purpose.

So far, so good. Now take a look at the change in the class constructor. Previously, the HLINE object was used to represent a price line. But now we will use the OBJ_RECTANGLE_LABEL object. This change allows us to set a maximum limit on the size of the horizontal lines. This way, they will not stretch from one edge of the chart to the other. The reason for using OBJ_RECTANGLE_LABEL instead of OBJ_TREND is precisely that trend lines require anchoring to time. This is something that can be difficult to set up correctly in some situations. In contrast, the OBJ_RECTANGLE_LABEL object uses Cartesian coordinates. This works perfectly for our purposes.

Most of the code remains unchanged, despite the difference in the object that will serve as our horizontal line. However, the actual changes were made in the procedure at line 74, that is, in the DispatchMessage function. At this stage, things might seem a little confusing at first, but there is no need to panic. Just pay attention, and everything will become clear.

Note that line 81 handles the message we created in the Defines.mqh file. However, this message exists here only because we are taking generalization to the extreme. Keep in mind that message handling is very simple. If the C_ElementsTrade class receives the evMsgSetFocus message, it checks whether the value of lparam matches the ticket being tracked by the class. In addition, the dparam parameter must be one of the event values. If it does not, the line will be set back to a smaller thickness. If the condition is met, the line thickness will be increased. It is simple.

"But wait a minute. How will the class know when the line gets thicker or thinner? Shouldn't this be defined somewhere?" Yes, dear reader. The actual task of signaling this is handled by the events described on lines 92, 98, and 104. Each of them is triggered only when there is a valid mouse click on the indicator. Note that the condition that allows these events to be triggered is specifically the name of the object that was clicked. "Yes, but why exactly this way? Wouldn't it be faster, simpler, and more practical to use a macro instead of triggering these events?" And once again, you are right, dear reader.

However, this would lead us to the very problem that arises when there is more than one position indicator on the chart. Or when we have pending order indicators. In this case, one indicator would not be able to tell another that it has lost focus and is no longer the active one. However, by using this specific message passing mechanism, we enable all indicators to work in perfect harmony and know exactly what to do and what not to do. Although this mechanism may not be very efficient in terms of implementation, it is quite practical and safe. Furthermore, from an implementation standpoint, it is significantly simpler than a more efficient mechanism.


Final Thoughts

You can see for yourself how this mechanism works by using a demo account. I recommend using a MetaQuotes demo account so you can see how the mechanism works when you have multiple open positions. Believe me, this will be very interesting to study.

If you do this, you'll probably notice that you run into some difficulties selecting lines, even if they're thicker than 1 pixel. Sometimes lines can be quite difficult to select. To make things even easier, we'll improve them a bit in the next article. In addition, of course, we'll start working directly on the chart, without needing to use the terminal system to create Stop Loss and Take Profit lines.

In the attached file, you'll find executable files so you can experiment with the material covered. This is for those who don't know how to compile the entire system.

File Description
Experts\Expert Advisor.mq5
Demonstrates the interaction between Chart Trade and the Expert Advisor (Mouse Study is required for this interaction).
Indicators\Chart Trade.mq5 Creates a window for configuring the order to be sent (Mouse Study is required for interaction)
Indicators\Market Replay.mq5 Creates controls for interacting with the replication/simulation service (Mouse Study is required for this interaction).
Indicators\Mouse Study.mq5 Enables interaction between graphical controls and the user (which is necessary both for the replication/simulation system and for live trading).
Indicators\Order Indicator.mq5 Responsible for displaying market orders and providing interaction with and control over them.
Indicators\Position View.mq5 Responsible for displaying market positions and providing interaction with and control over them.
Services\Market Replay.mq5 Creates and maintains the market replication/simulation service (the main file of the entire system).

Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/13274

Attached files |
Anexo.zip (779.24 KB)
Machine Learning Under Constraint (Part 1): A Configurable Rule Set for Prop-Firm Position Sizing Machine Learning Under Constraint (Part 1): A Configurable Rule Set for Prop-Firm Position Sizing
Hardcoded prop-firm rules lock the sizer to one program. This article factors those rules into a PropFirmRuleSet and refactors PropFirmAccountState and the sizing modifiers to consume it, including dynamic versus fixed daily limits and the news-window profit-credit haircut. Parity against the original FundedNext behavior is validated on a simulated equity path, so you can retarget sizing by configuration instead of rewriting code.
From Basic to Intermediate: Queues, Lists, and Trees (II) From Basic to Intermediate: Queues, Lists, and Trees (II)
This is an article that you, dear reader, should study carefully. That is due to the nature of the material presented here. Although we have tried to present the material as simply and informatively as possible, the information provided here can certainly seem quite complex to those who are just beginning to learn programming. Nevertheless, this is no reason to lose heart or ignore what is explained here, as this article will establish a link between two completely different, though closely related, topics.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Building a Bar Replay Tool in MQL5 Building a Bar Replay Tool in MQL5
This article shows how to build an interactive bar replay tool in MQL5 for MetaTrader 5 that reveals historical candles one by one without exposing future data. You will implement custom candles with DRAW COLOR CANDLES, an event-driven engine with OnChartEvent and OnTimer, a dashboard with Play/Pause, a draggable replay anchor, and Buy/Sell paper trading with SL/TP lines, while keeping the active bar in view to practice discretionary analysis and execution.