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

Market Simulation: Position View (VII)

MetaTrader 5Tester |
190 0
Daniel Jose
Daniel Jose

Introduction

Hello, everyone, and welcome to another article in this series on building a replay/simulation system.

In the previous article, Market Simulation: Position View (VI), we demonstrated how to make the Expert Advisor tell the position indicator whether it should be updated or, in most cases, whether it should remain on the chart or be removed. Although what we covered in the previous article is quite interesting in terms of how we can start developing certain components for MetaTrader 5, it is still not particularly practical. This is because we cannot interact directly with the position indicator to specify that a price line—whether a Stop Loss or Take Profit line—should be moved or, at the very least, removed for that position.

However, we still have one more issue, and it might be even more interesting to address it right now: the possibility of closing a position by interacting with the position indicator in some way. This will eliminate the need to keep the terminal window open on the Trade tab at all times, which can be useful during testing but becomes unnecessary if we can interact with the position indicator.

In this article, we'll start making some improvements to the position indicator so that we can interact with it and modify price lines or close a position directly through the position indicator. Before we move on to the implementation, there are a few things worth clarifying, especially for those who aren't familiar with this. Under no circumstances is it possible to use the indicator to change anything on the trading server. This is because MetaTrader 5 has a security system in place that allows only Expert Advisors to modify orders and positions.

Although MetaTrader 5 itself can be used for this in its standard default configuration, no application other than an Expert Advisor can manipulate orders or positions. Therefore, from this point on, we will create a communication mechanism between the position indicator and the Expert Advisor so that it appears as though the position indicator is affecting the orders or positions held on the server. But, as I just said, it's just an illusion.

If you're enthusiastic and plan to use what I'm about to explain, you should know that you'll need to set up the applications to work together in MetaTrader 5. Don't think that a single application can do everything shown here, because that's not realistic—it's just an illusion we'll create. If implemented correctly, this illusion will allow you to work or trade in MetaTrader 5 in a very pleasant and comfortable way. So, let's move on to what really matters.


Closing a Position by Interacting with the Position Indicator

Before we begin implementing certain mechanisms, let's create a simple safety mechanism. Because even on a demo account, we want to implement a method for closing a position right from the start, without resorting to any mechanisms other than the position indicator.

It's relatively simple and easy to understand. In practice, we only need to create an object on the chart and place it at a specific location. When this object is clicked or any other event occurs, it will cause the position indicator to generate an event. The Expert Advisor will intercept this event. Inside the Expert Advisor, we will create the necessary mechanisms so that this event, coming from the position indicator, can be used to send a request to the server. This request will be used to close the position that the position indicator displays on the chart.

It is worth noting that, although this may seem complicated at first glance, it is actually relatively simple to implement—but only if you follow this series of articles on developing a replay/simulation system. The reason is that we've already done something very similar before, when we created the Chart Trade indicator. What we're going to create now is a mechanism very similar to the Chart Trade indicator's mechanism, but there are some minor differences mean that the position indicator is implemented differently from the Chart Trade indicator. Let's get started so you can understand what I'm talking about.

So, first, let's add a new event to the Defines.mqh file. The updated file 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.                         };
50. //+------------------------------------------------------------------+
51. enum EnumPriority {                          //Priority list on objects
52.             ePriorityNull = -1,
53.             ePriorityDefault = 0,
54.             ePriorityChartTrade = 5,
55.             ePriorityOrders = 10
56.                         };
57. //+------------------------------------------------------------------+

Defines.mqh

The new event is on line 48. I think it's perfectly clear exactly what will be done. But why not use the Chart Trade indicator event declared on line 42? The reason is simple: I don't want to limit the position indicator or the Expert Advisor to just one account type. If we were certain that the application would work only with NETTING accounts, we could use the event on line 42 without any problems. However, if you use the same applications—that is, the position indicator together with the Expert Advisor—on a HEDGING account, then when you attempt to close one position, all positions will be closed at the same time. This is one of the differences in the implementation of these two indicators.

Great, now that you know what we're going to do, we need to modify the position indicator code as well as the Expert Advisor code so that the event is interpreted and handled correctly. Okay, you might get worried and think, "Wow, this is going to be very time-consuming. We'll have to make a lot of changes to the code." But if you thought that, it means you don't really understand the code. This task will require only a few changes. Let's first take a look at the changes that need to be made to the Expert Advisor code. These are shown in the following code:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. class C_Orders
007. {
008.     protected:
009. //+------------------------------------------------------------------+
010. inline const ulong GetMagicNumber(void) const { return m_Base.MagicNumber; }
011. //+------------------------------------------------------------------+
012.         bool ClosePosition(const ulong ticket)
013.             {
014.                 bool     IsBuy;
015.                 string szContract;
016.                 
017.                 if    (!PositionSelectByTicket(ticket)) return false;
018.                 IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
019.                 szContract = PositionGetString(POSITION_SYMBOL);
020.                 ZeroMemory(m_Base.TradeRequest);
021.                 m_Base.TradeRequest.action     = TRADE_ACTION_DEAL;
022.                 m_Base.TradeRequest.type       = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
023.                 m_Base.TradeRequest.price      = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (int)SymbolInfoInteger(szContract, SYMBOL_DIGITS));
024.                 m_Base.TradeRequest.position   = ticket;
025.                 m_Base.TradeRequest.symbol     = szContract;
026.                 m_Base.TradeRequest.volume     = PositionGetDouble(POSITION_VOLUME);
027.                 m_Base.TradeRequest.deviation  = 1000;
028.                 
029.                 return SendToPhysicalServer() != 0;
030.             };
031. //+------------------------------------------------------------------+    
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stBase
035.         {
036.             MqlTradeRequest TradeRequest;
037.             ulong           MagicNumber;
038.             bool            bTrash;
039.         }m_Base;
040. //+------------------------------------------------------------------+
041.         struct stChartTrade
042.         {
043.             struct stEvent
044.             {
045.                 EnumEvents  ev;
046.                 string      szSymbol,
047.                             szContract;
048.                 bool        IsDayTrade;
049.                 ushort      Leverange;
050.                 double      PointsTake,
051.                             PointsStop;
052.             }Data;
053. //---
054.             bool Decode(const EnumEvents ev, const string sparam)
055.                 {
056.                     string Res[];
057.         
058.                     if (StringSplit(sparam, '?', Res) != 7) return false;
059.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
060.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
061.                     else return false;
062.                     
063.                     return true;
064.                 }
065. //---
066.         }m_ChartTrade;
067. //+------------------------------------------------------------------+
068.         ulong SendToPhysicalServer(void)
069.             {
070.                 MqlTradeCheckResult TradeCheck;
071.                 MqlTradeResult      TradeResult;
072.                 
073.                 ZeroMemory(TradeCheck);
074.                 ZeroMemory(TradeResult);
075.                 if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
076.                 {
077.                     PrintFormat("Order System - Check Error: %d", GetLastError());
078.                     return 0;
079.                 }
080.                 m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
081.                 if (TradeResult.retcode != TRADE_RETCODE_DONE)
082.                 {
083.                     PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
084.                     return 0;
085.                 };
086.                 
087.                 return TradeResult.order;
088.             }
089. //+------------------------------------------------------------------+    
090.         ulong ToMarket(const ENUM_ORDER_TYPE type)
091.             {
092.                 double price  = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID));
093.                 double vol    = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP);
094.                 uchar  nDigit = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS);
095.                 
096.                 ZeroMemory(m_Base.TradeRequest);
097.                 m_Base.TradeRequest.magic         = m_Base.MagicNumber;
098.                 m_Base.TradeRequest.symbol        = m_ChartTrade.Data.szContract;
099.                 m_Base.TradeRequest.price         = NormalizeDouble(price, nDigit);
100.                 m_Base.TradeRequest.action        = TRADE_ACTION_DEAL;
101.                 m_Base.TradeRequest.sl            = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit);
102.                 m_Base.TradeRequest.tp            = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit);
103.                 m_Base.TradeRequest.volume        = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit);
104.                 m_Base.TradeRequest.type          = type;
105.                 m_Base.TradeRequest.type_time     = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
106.                 m_Base.TradeRequest.stoplimit     = 0;
107.                 m_Base.TradeRequest.expiration    = 0;
108.                 m_Base.TradeRequest.type_filling  = ORDER_FILLING_RETURN;
109.                 m_Base.TradeRequest.deviation     = 1000;
110.                 m_Base.TradeRequest.comment       = "Order Generated by Experts Advisor.";
111. 
112.                 MqlTradeRequest TradeRequest[1];
113. 
114.                 TradeRequest[0] = m_Base.TradeRequest;
115.                 ArrayPrint(TradeRequest);
116. 
117.                 return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0);
118.             };
119. //+------------------------------------------------------------------+
120.         void CloseAllsPosition(void)
121.             {
122.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
123.                 {
124.                     if (PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue;
125.                     if (PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue;
126.                     ClosePosition(PositionGetInteger(POSITION_TICKET));
127.                 }
128.             };
129. //+------------------------------------------------------------------+    
130.     public    :
131. //+------------------------------------------------------------------+
132.         C_Orders(const ulong magic)
133.             {
134.                 m_Base.MagicNumber = magic;
135.             }
136. //+------------------------------------------------------------------+    
137.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
138.             {
139.                 switch (id)
140.                 {
141.                     case CHARTEVENT_CUSTOM + evChartTradeBuy     :
142.                     case CHARTEVENT_CUSTOM + evChartTradeSell    :
143.                     case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
144.                         if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev)
145.                         {
146.                             case evChartTradeBuy:
147.                                 ToMarket(ORDER_TYPE_BUY);
148.                                 break;
149.                             case evChartTradeSell:
150.                                 ToMarket(ORDER_TYPE_SELL);
151.                                 break;
152.                             case evChartTradeCloseAll:
153.                                 CloseAllsPosition();
154.                                 break;
155.                         }
156.                         break;
157.                     case CHARTEVENT_CUSTOM + evMsgClosePositionEA:
158.                         ClosePosition((ulong)StringToInteger(sparam));
159.                         break;
160.                 }
161.             }
162. //+------------------------------------------------------------------+    
163. };
164. //+------------------------------------------------------------------+

C_Orders.mqh

Although we are showing the complete code for the C_Orders class so you can understand what is going on if you have not been following this series, we have added only three lines to the class code. These are lines 157, 158, and 159. And that's all that needed to be added to the Expert Advisor code so that it could process a request to close a position. Please note: we do not filter any data here at all. Therefore, this same event can be generated, for example, by a service or even by a script. We do not impose any restrictions on who exactly gives the Expert Advisor the command to perform the action, which in this case is closing a position. The only thing we really need to worry about is specifying, in the sparam field, the position identifier for the position we want to close. If the ticket exists, the position will be closed.

Now pay attention to the point I mentioned earlier: we could not use the evChartTradeCloseAll event because the call that needs to be made is different from that used in evChartTradeCloseAll. The reason is that this implementation is designed to work with any account type, whether a NETTING account or a HEDGING account. All right. The part related to the Expert Advisor has already been implemented and certainly works, because this code, which we use in the C_Orders class, has been tested for quite some time now.

We can focus on the part that you might find the most difficult. But you'll see that it's all very simple. So, to show that the implementation in the position indicator is fairly simple, I won't make any drastic changes to the indicator code right now. I'll simply add everything needed so that, when interacting with the position indicator, a request is sent to the Expert Advisor to close the specified position. This can be achieved very easily, as you can see below.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_SufixTake        "Take"
005. #define def_SufixStop        "Stop"
006. //+------------------------------------------------------------------+
007. #define def_NameBtnClose     m_Infos.szShortName + "Close"
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_Terminal.mqh"
014. //+------------------------------------------------------------------+
015. class C_IndicatorPosition : private C_Terminal
016. {
017.     private    :
018.         struct st00
019.         {
020.             ulong     ticket;
021.             color     corPrice, corTake, corStop;
022.             string    szShortName;
023.         }m_Infos;
024. //+------------------------------------------------------------------+
025.         inline void MoveLineInfos(const string szDefine, const double price)
026.         {
027.             string szName = m_Infos.szShortName + szDefine;
028.             int x, y;
029.             
030.             if (price <= 0) ObjectDelete(0, szName);
031.             else
032.             {
033.                 if (ObjectFind(0, szName) < 0)
034.                 {
035.                     if (szDefine == def_SufixTake) CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point."); else
036.                     if (szDefine == def_SufixStop) CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point."); else
037.                     {
038.                         CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price.");
039.                         CreateObjectGraphics(def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityOrders + 1));
040.                         ObjectSetString(0, def_NameBtnClose, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
041.                     }
042.                 }                    
043.                 ObjectSetDouble(0, szName, OBJPROP_PRICE, price);
044.                 if (szDefine == NULL)
045.                 {
046.                     ChartTimePriceToXY(0, 0, 0, price, x, y);
047.                     ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, 130);
048.                     ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y - 8);
049.                 }
050.             }
051.         }
052. //+------------------------------------------------------------------+
053.         void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n")
054.         {            
055.             szObjName = m_Infos.szShortName + szObjName;
056.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
057.             ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription);
058.             ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription);
059.             ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
060.         }
061. //+------------------------------------------------------------------+
062.     public    :
063. //+------------------------------------------------------------------+
064.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
065.             :C_Terminal()
066.         {
067.             ZeroMemory(m_Infos);
068.             m_Infos.corPrice = corPrice;
069.             m_Infos.corTake  = corTake;
070.             m_Infos.corStop  = corStop;
071.         }
072. //+------------------------------------------------------------------+
073.         ~C_IndicatorPosition()
074.         {
075.             if (m_Infos.ticket != 0)
076.                 ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket));
077.         }
078. //+------------------------------------------------------------------+
079.         bool CheckCatch(ulong ticket)
080.         {
081.             m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket);            
082.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
083.             if (ObjectFind(0, m_Infos.szShortName) >= 0)
084.             {
085.                 m_Infos.ticket = 0;
086.                 return false;
087.             }
088.             IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
089.             EventChartCustom(0, evUpdate_Position, ticket, 0, "");
090.             
091.             return true;
092.         }
093. //+------------------------------------------------------------------+
094.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
095.         {
096.             static double price = 0;
097.             
098.             switch (id)
099.             {
100.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
101.                     if (lparam != m_Infos.ticket) return;
102.                     if (!PositionSelectByTicket(m_Infos.ticket))
103.                     {
104.                         ChartIndicatorDelete(0, 0, m_Infos.szShortName);
105.                         return;
106.                     };
107.                     MoveLineInfos(NULL, price = PositionGetDouble(POSITION_PRICE_OPEN));
108.                     MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP));
109.                     MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL));
110.                     break;
111.                 case CHARTEVENT_CHART_CHANGE:
112.                     MoveLineInfos(NULL, price);
113.                     break;
114.                 case CHARTEVENT_OBJECT_CLICK:
115.                     if (sparam == def_NameBtnClose)
116.                         EventChartCustom(0, evMsgClosePositionEA, 0, 0, m_Infos.szShortName);
117.                     break;
118.             }
119.             ChartRedraw();
120.         }
121. //+------------------------------------------------------------------+
122. };
123. //+------------------------------------------------------------------+
124. #undef def_Btn_Close
125. #undef def_PathBtns
126. //+------------------------------------------------------------------+
127. #undef def_NameBtnClose
128. //+------------------------------------------------------------------+
129. #undef def_SufixTake
130. #undef def_SufixStop
131. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

So, here is the code for the C_IndicatorPosition class. Please note that, in terms of implementation, the changes are very minor. However, this code can place an object on the chart that you can interact with and use to request that the Expert Advisor close a position. But how does this happen? Let's now analyze this code before we make a few improvements to it. This code is not yet suitable for further development of our indicator. But, dear reader, it’s important that you understand this before moving on to more complex code.

Note that line 07 contains a definition. Its purpose is to define the name of the object we will interact with to close a position. Next, between lines 09 and 11, we have what needs to be prepared locally. This will be required if you want to create your own template. If you don't want to do this, don't worry—we'll soon make the applications available with the article, just as we did in the previous article.

All right, let's continue. On line 09, we specify the location of the bitmap image. On line 10, we specify what the bitmap image represents. In this case, you'll need to prepare an image that is 16 x 16 pixels in size. However, you can use a different image if you make the appropriate changes to the code. Later, we'll see exactly where in the code you'll need to make this change. For now, let's focus on line 11. In this line, we tell the MQL5 compiler that we want to include the bitmap as a resource in the executable file. This will eliminate the need to attach the bitmap to the rest of the application after compilation, which greatly simplifies the task for both us and the end user.

Great, we are moving through the code without major changes, with two exceptions. For now, we won't worry about the procedure on line 25, because that is where the changes were made. Let's move directly to line 94, where the code responsible for handling messages from MetaTrader 5 is implemented.

Now pay close attention, because understanding what happens in the procedure defined on line 94 will be very important for what we'll see next. Please note that a static variable has been added on line 96. It will eventually be removed. But for now, this allows us to locally store the price at which the position was opened and keep it in memory. Please note that this same variable is initialized on line 107. All right. Now we have the price at which the position was opened. It is important to note that this value will remain constant the whole time. But we need this at a very specific moment. And that moment occurs on line 112. Please note that this line is located inside the CHARTEVENT_CHART_CHANGE event handler. The reason is simple. When you change the price scale on a chart, the price lines change along with the scale.

However, there is a problem that we'll see later when we return to line 25: the button we place on the chart does not move along with the price lines. To solve this problem, we send a request, as shown on line 112, to update the button's position on the chart. That is why we need to know the price at which the position was opened. If, every time the price scale changed, we had to look up the price at which the position was opened, we would introduce a significant delay in the execution of the applications running on the chart. Since the position price does not change, we can simplify this as much as possible.

However, in the DispatchMessage procedure, we are actually interested in the next block, that is, the code implemented on line 114. Please note that this is where we check whether the object has been clicked. When MetaTrader 5 notifies us that this has happened, it will specify exactly which object was clicked. The object name is passed to the sparam variable. Thus, on line 115, we check whether the name matches the name of the object created at the very beginning, that is, on line 07. If the check performed on line 115 evaluates to true, on line 116 we request that MetaTrader 5 send a custom event so that the Expert Advisor can catch it later.

Analyze the value of this event. These are exactly the values the Expert Advisor expects in order to interpret a request to close a position. That's interesting, isn't it? Now let's take a look at how the object (or bitmap) representing the button for closing a position is created and positioned on the chart. To do this, for now we will focus on line 25, where the key part is executed.

Please note that we have made several changes here compared to the previous version. As the first step, the variables x and y were added on line 28. After that, on line 38, we created a string containing the price, just as we did earlier. However, since we need one more object, which in this case will be a button for closing a position, we create it on line 39. It is important to note how we do this and which objects we create. That is, an OBJ_BITMAP_LABEL object. For correct positioning, this object requires X and Y coordinate values. We cannot use price coordinates. Immediately after creating the object, on line 40, we instruct MetaTrader 5 to use the image that is included as a resource in the executable file. It is at this point, not on line 11, that we attach the image included in the executable file and stop using the image from the directory. But up to this point, we have only created the object; we still have not displayed it on the screen.

To place it at the correct point, we need to convert the price-time coordinates to Cartesian coordinates. Fortunately, MQL5 has a helper function that is used on line 46. There, we convert the price into the appropriate Y-axis coordinate. This is the simplest part, but some minor precautions should be taken. To understand this, take a look at what we're doing on line 48. There, I subtract eight from the value returned by the function on line 46. Why am I doing this subtraction, and why am I using the number eight? We perform this subtraction because the OBJ_BITMAP_LABEL object is anchored to the top-left corner rather than the center.

Since I did not set the object's anchor to its center, I need to adjust its position. That is why we need to subtract eight. Since the bitmap image we're using is 16 x 16 pixels, we need to subtract eight pixels to center it. It's that simple. Once this is done, we can interact with the position indicator to close the position by clicking the object we have just created and positioned. But we can do much more. However, to prevent the code from becoming unmaintainable, we need to improve it, and that's what we'll do. This will give us much more functionality with minimal code. Nevertheless, to properly separate the topics, we'll cover this in a new section.


Further Improving the Indicator

If you pause and think about it for a moment, you'll notice that we already have an object that allows us to close a position. The price lines are already shown on the chart. We have the ability to send requests from the position indicator to the Expert Advisor. All of this with only the minimum necessary changes. But how could we apply the same type of interaction used for closing a position to the Stop Loss and Take Profit lines, in order to remove the loss limit and the profit limit? And to do this directly by interacting with the position indicator, without using the Trade tab in the MetaTrader 5 terminal.

You might imagine that this would be simple. It would be enough to duplicate the code used to close a position and adapt it to remove the Stop Loss or Take Profit level. In a sense, that's exactly how you should view it. However, imagine the following scenario: if you duplicate the code to achieve your goal, you will succeed. However, if you want to continue improving the indicator, the amount of work will increase significantly, since you'll have to update the code in all the places where it has been duplicated. In many cases, this task is relatively simple and requires little effort.

But what if we did it in a slightly more convenient way? Or, to be more precise, make sure you don't have to worry about it at all. Would it be enough simply to specify that a line represents a particular element and have the code itself take care of creating it correctly? With all objects already included and functional. That would be great, wouldn't it? All right. That's what we're going to do right now. We will create a mechanism to automate this. But this is a task that requires attention. And I want you to understand what I'm doing and why I'm doing it. We'll go through this step by step. This way, you can easily modify the code if you want to.

The first thing we'll do is add a few new events to the Defines.mqh file. The file looks like as follows. With the new events already defined.

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.                         };
52. //+------------------------------------------------------------------+
53. enum EnumPriority {                          //Priority list on objects
54.             ePriorityNull = -1,
55.             ePriorityDefault = 0,
56.             ePriorityChartTrade = 5,
57.             ePriorityOrders = 10
58.                         };
59. //+------------------------------------------------------------------+

Defines.mqh

Please note that it's all very simple and practical. It's not hard to see what was done here. After that, we'll move on to the next step. At this first stage, the work turns out to be a little more time-consuming. We'll take our time so that you can really understand what's going on. All right, we want to add a mechanism that will allow us to remove the Stop Loss and Take Profit lines. But it is not enough to simply add this to the message block, since we cannot execute the request. Therefore, we need to modify the Expert Advisor code. To make this change, we'll go straight to the key part—the C_Orders.mqh header file. New code will be added to this file, as shown below.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. class C_Orders
007. {
008.     protected:
009. //+------------------------------------------------------------------+
010. inline const ulong GetMagicNumber(void) const { return m_Base.MagicNumber; }
011. //+------------------------------------------------------------------+
012.         bool ClosePosition(const ulong ticket)
013.             {
014.                 bool   IsBuy;
015.                 string szContract;
016.                 
017.                 if    (!PositionSelectByTicket(ticket)) return false;
018.                 IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
019.                 szContract = PositionGetString(POSITION_SYMBOL);
020.                 ZeroMemory(m_Base.TradeRequest);
021.                 m_Base.TradeRequest.action    = TRADE_ACTION_DEAL;
022.                 m_Base.TradeRequest.type      = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
023.                 m_Base.TradeRequest.price     = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (int)SymbolInfoInteger(szContract, SYMBOL_DIGITS));
024.                 m_Base.TradeRequest.position  = ticket;
025.                 m_Base.TradeRequest.symbol    = szContract;
026.                 m_Base.TradeRequest.volume    = PositionGetDouble(POSITION_VOLUME);
027.                 m_Base.TradeRequest.deviation = 1000;
028.                 
029.                 return SendToPhysicalServer() != 0;
030.             };
031. //+------------------------------------------------------------------+    
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stBase
035.         {
036.             MqlTradeRequest TradeRequest;
037.             ulong           MagicNumber;
038.             bool            bTrash;
039.         }m_Base;
040. //+------------------------------------------------------------------+
041.         struct stChartTrade
042.         {
043.             struct stEvent
044.             {
045.                 EnumEvents  ev;
046.                 string      szSymbol,
047.                             szContract;
048.                 bool        IsDayTrade;
049.                 ushort      Leverange;
050.                 double      PointsTake,
051.                             PointsStop;
052.             }Data;
053. //---
054.             bool Decode(const EnumEvents ev, const string sparam)
055.                 {
056.                     string Res[];
057.         
058.                     if (StringSplit(sparam, '?', Res) != 7) return false;
059.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
060.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
061.                     else return false;
062.                     
063.                     return true;
064.                 }
065. //---
066.         }m_ChartTrade;
067. //+------------------------------------------------------------------+
068.         ulong SendToPhysicalServer(void)
069.             {
070.                 MqlTradeCheckResult  TradeCheck;
071.                 MqlTradeResult       TradeResult;
072.                 
073.                 ZeroMemory(TradeCheck);
074.                 ZeroMemory(TradeResult);
075.                 if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
076.                 {
077.                     PrintFormat("Order System - Check Error: %d", GetLastError());
078.                     return 0;
079.                 }
080.                 m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
081.                 if (TradeResult.retcode != TRADE_RETCODE_DONE)
082.                 {
083.                     PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
084.                     return 0;
085.                 };
086.                 
087.                 return TradeResult.order;
088.             }
089. //+------------------------------------------------------------------+    
090.         ulong ToMarket(const ENUM_ORDER_TYPE type)
091.             {
092.                 double price  = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID));
093.                 double vol    = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP);
094.                 uchar  nDigit = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS);
095.                 
096.                 ZeroMemory(m_Base.TradeRequest);
097.                 m_Base.TradeRequest.magic        = m_Base.MagicNumber;
098.                 m_Base.TradeRequest.symbol       = m_ChartTrade.Data.szContract;
099.                 m_Base.TradeRequest.price        = NormalizeDouble(price, nDigit);
100.                 m_Base.TradeRequest.action       = TRADE_ACTION_DEAL;
101.                 m_Base.TradeRequest.sl           = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit);
102.                 m_Base.TradeRequest.tp           = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit);
103.                 m_Base.TradeRequest.volume       = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit);
104.                 m_Base.TradeRequest.type         = type;
105.                 m_Base.TradeRequest.type_time    = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
106.                 m_Base.TradeRequest.stoplimit    = 0;
107.                 m_Base.TradeRequest.expiration   = 0;
108.                 m_Base.TradeRequest.type_filling = ORDER_FILLING_RETURN;
109.                 m_Base.TradeRequest.deviation    = 1000;
110.                 m_Base.TradeRequest.comment      = "Order Generated by Experts Advisor.";
111. 
112.                 MqlTradeRequest TradeRequest[1];
113. 
114.                 TradeRequest[0] = m_Base.TradeRequest;
115.                 ArrayPrint(TradeRequest);
116. 
117.                 return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0);
118.             };
119. //+------------------------------------------------------------------+
120.         void CloseAllsPosition(void)
121.             {
122.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
123.                 {
124.                     if (PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue;
125.                     if (PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue;
126.                     ClosePosition(PositionGetInteger(POSITION_TICKET));
127.                 }
128.             };
129. //+------------------------------------------------------------------+    
130.         void ModifyValueSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
131.             {
132.                 ZeroMemory(m_Base.TradeRequest);            
133.                 MqlTradeRequest TradeRequest[1];
134. 
135.                 m_Base.TradeRequest.magic    = m_Base.MagicNumber;
136.                 m_Base.TradeRequest.action   = TRADE_ACTION_SLTP;
137.                 m_Base.TradeRequest.symbol   = symbol;
138.                 m_Base.TradeRequest.position = ticket;
139.                 m_Base.TradeRequest.sl       = sl;
140.                 m_Base.TradeRequest.tp       = tp;
141.                 
142.                 TradeRequest[0] = m_Base.TradeRequest;
143.                 ArrayPrint(TradeRequest);
144.                 
145.                 SendToPhysicalServer();
146.             }
147. //+------------------------------------------------------------------+    
148.     public    :
149. //+------------------------------------------------------------------+
150.         C_Orders(const ulong magic)
151.             {
152.                 m_Base.MagicNumber = magic;
153.             }
154. //+------------------------------------------------------------------+    
155.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
156.             {
157.                 switch (id)
158.                 {
159.                     case CHARTEVENT_CUSTOM + evChartTradeBuy     :
160.                     case CHARTEVENT_CUSTOM + evChartTradeSell    :
161.                     case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
162.                         if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev)
163.                         {
164.                             case evChartTradeBuy:
165.                                 ToMarket(ORDER_TYPE_BUY);
166.                                 break;
167.                             case evChartTradeSell:
168.                                 ToMarket(ORDER_TYPE_SELL);
169.                                 break;
170.                             case evChartTradeCloseAll:
171.                                 CloseAllsPosition();
172.                                 break;
173.                         }
174.                         break;
175.                     case CHARTEVENT_CUSTOM + evMsgClosePositionEA:
176.                         ClosePosition((ulong)(lparam));
177.                         break;
178.                     case CHARTEVENT_CUSTOM + evMsgCloseTakeProfit:
179.                         ModifyValueSLTP((ulong)(lparam), sparam, dparam, 0);
180.                         break;
181.                     case CHARTEVENT_CUSTOM + evMsgCloseStopLoss:
182.                         ModifyValueSLTP((ulong)(lparam), sparam, 0, dparam);
183.                         break;
184.                 }
185.             }
186. //+------------------------------------------------------------------+    
187. };
188. //+------------------------------------------------------------------+

C_Orders.mqh

The code shown above is complete, just like all the others. Looking at this, you might think, "Well, I don't see much of a difference compared to the previous version." Actually, I always try to avoid making very drastic changes to the code. However, some changes have been made here. These changes are intended to provide the support we need at the current and next stages of development. But let's first focus on this step, which involves removing the Stop Loss and Take Profit lines. Please note that on line 130 of the code above, we now have a new procedure. This procedure is used to change the Stop Loss and Take Profit values for an open position. Please take note of this. At the moment, we are not working with orders in the order book, but with an already open position. There is a difference between these two situations.

Essentially, we receive certain parameters and send a request to the trading server to change the Take Profit and Stop Loss levels. On line 143, we print the request sent to the server in the terminal. This allows us to inspect the request and analyze how the system works. All right, since this procedure is fairly simple and only requires passing arguments, we can move on to the next section where the code was modified. This change appears in the procedure shown on line 155.

At this point, we will actually process the requests received by the Expert Advisor. Keep in mind that the request can come from any source—it doesn't necessarily have to be the position indicator I'm showing. So study what I'm showing you so you can create your own solution. Please note that the code for this DispatchMessage procedure remains virtually the same as before. Take a closer look. You'll see that something has changed here. We'll see the reason in the next article.

Please note that on line 175, where we check whether the received message is a request to close a position, the implementation of closing the position has been changed. In other words, line 176 was slightly modified. Previously, the identifier was passed via the sparam variable, but now I'm using the lparam variable. Please note that there is a reason for this, but you'll understand it better in the next article. Now let's look at two other checks that are performed to identify the received message. The first check is on line 178, where we check whether the message corresponds to removing the Take Profit level. The second check is on line 181, where we verify whether the message is a request to remove the Stop Loss level.

In both cases, we have the same target: the ModifyValueSLTP procedure, which is used to change a position's Stop Loss and Take Profit levels. Please note what exactly is passed in each parameter. The only difference here is where the dparam parameter is used. In one case, this applies to Take Profit, and in the other, to Stop Loss. Thus, we can remove one of the levels, since when one of them is assigned a zero value, it will be removed. This happens because the trading server interprets a zero value as meaning that the limit—whether it is Take Profit or Stop Loss—simply does not exist. This concludes the necessary changes to the Expert Advisor's code; no further modifications are needed. At least for now.


Concluding Thoughts

In this article, I demonstrated a fairly simple and effective way to close a position by combining the Mouse Study indicator with the position indicator, in conjunction with a properly programmed Expert Advisor. The entire implementation was carried out in a rather elegant way, since it did not require any major changes to the code. I also showed the entire part that had to be implemented in the Expert Advisor's code so that we could create a mechanism for removing the Take Profit and Stop Loss lines.

However, because of the larger number of changes that will need to be made to the position indicator's code, I will not include the modified code in this article. I will describe and explain this in detail in the next article. I'll do this so that you can truly understand how and why this code works—but, first and foremost, why MetaTrader 5 can correctly interpret and display information during trading. You will see that, with MetaTrader 5, you do not need to worry about certain details. Most of what we actually need to do will involve instructing MetaTrader 5 to send a message; the rest will happen naturally, without much effort or hassle.

I hope that, whether you have a lot of experience with MQL5 or are just getting started, you understand that my goal here is to teach you how we can implement solutions like these. If this has piqued your interest, be sure to carefully review this article and the code, because in the next one we'll cover the part related to the position indicator.

File Description
Experts\Expert Advisor.mq5
Shows the interaction between Chart Trade and the Expert Advisor on the chart. (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 this interaction.)
Indicators\Market Replay.mq5 Creates controls for interacting with the replay/simulation service. (Mouse Study is required for interaction.)
Indicators\Mouse Study.mq5 Provides interaction between graphical controls and the user, which is required both for the replay/simulation system to operate and in the live market.
Indicators\Order Indicator.mq5 Responsible for generating market orders, enabling interaction with them, and controlling them.
Indicators\Position View.mq5 Responsible for displaying open positions and enabling interaction with and control over them.
Services\Market Replay.mq5 Creates and maintains the market replay/simulation service (the main file of the entire system).

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

Attached files |
Anexo.zip (779.24 KB)
Dandelion Optimizer (DO) Dandelion Optimizer (DO)
The Dandelion Optimizer (DO) turns the simple flight of a seed carried by the wind into a mathematical search strategy. The three phases — vortex rising, drift toward the center of the population, and landing along a Lévy-flight trajectory — form an elegant metaphor that yields interesting results in practice.
Combining LLM, CatBoost, and Quantum Computing into a Unified Trading System Combining LLM, CatBoost, and Quantum Computing into a Unified Trading System
The article proposes a synthesis of new technologies to overcome the limitations of classical indicators in market data analytics. It shows how language models and quantum encoding can reveal hidden market patterns that traditional methods overlook. The experiment confirms the value of new technologies and proposes an updated analysis methodology aligned with the current state of computational innovation.
Reinforcement Learning Meets MetaTrader 5: A Complete Pipeline for Training, Validating and Honestly Evaluating a Gold Trading Bot Reinforcement Learning Meets MetaTrader 5: A Complete Pipeline for Training, Validating and Honestly Evaluating a Gold Trading Bot
This article presents a complete RL trading pipeline for XAUUSD: a supervised signal baseline with triple-barrier labels, PPO training, purged walk-forward validation with embargo, multi-seed checks, and contract-guarded deployment with normalization. It includes runnable code for data validation, features, environment, training, and broker‑based reconciliation. The live demo over 763 closed trades showed no statistically significant edge, and the methods highlight where information and costs, not architecture, set performance limits.
From Basic to Intermediate: Navigating the Sandbox From Basic to Intermediate: Navigating the Sandbox
In this article, we'll look at two ways to inspect the contents of the sandbox and even interact with it, using MetaTrader 5 as the base platform. Understanding the material in this article is essential to understanding what will be covered in subsequent articles.