Русский Español Português
preview
Developing a Replay System (Part 75): New Chart Trade (II)

Developing a Replay System (Part 75): New Chart Trade (II)

MetaTrader 5Examples |
314 0
Daniel Jose
Daniel Jose

Introduction

In the previous article Developing a Replay System (Part 74): New Chart Trade (I), I focused primarily on explaining the code for the Chart Trade indicator. We discussed why you might choose one approach over another when programming the Chart Trade. We also covered several aspects of the code. However, I left out a key part: the core code itself.

One of the reasons I didn't include the main code earlier is that over 100 lines were removed from it. So I had to think of a simple way to demonstrate what changed in the code. The best approach I found is the one I'm presenting in this article.

Here, we'll take a closer look at the main code for the Chart Trade. Keep in mind that, if you wish, you can embed this code directly into an Expert Advisor. Though, that will require a few adjustments, which I'll explain today. Don't forget the reasons discussed in the previous article about why the code is placed in an indicator instead of an Expert Advisor. Still, you are free to use the code in whatever way best suits your needs.

So, without further delay, let's get started.


Understanding the Source Code of the C_ChartFloatingRAD Class

The source code for the C_ChartFloatingRAD class is located in the header file of the same name. Below, you'll find almost the complete code for this class - everything except the DispatchMessage procedure.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "../Auxiliar/C_Mouse.mqh"
005. #include "C_AdjustTemplate.mqh"
006. //+------------------------------------------------------------------+
007. #define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A)
008. #define macro_CloseIndicator(A)   {           \
009.                OnDeinit(REASON_INITFAILED);   \
010.                SetUserError(A);               \
011.                return;                        \
012.                                  }
013. //+------------------------------------------------------------------+
014. class C_ChartFloatingRAD : private C_Terminal
015. {
016.    private   :
017.       enum eObjectsIDE {MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL};
018.       struct st00
019.       {
020.          short    x, y, minx, miny,
021.                   Leverage;
022.          string   szObj_Chart,
023.                   szObj_Editable,
024.                   szFileNameTemplate;
025.          long     WinHandle;
026.          double   FinanceTake,
027.                   FinanceStop;
028.          bool     IsMaximized,
029.                   IsDayTrade,
030.                   IsSaveState;
031.          struct st01
032.          {
033.             short  x, y, w, h;
034.             color  bgcolor;
035.             int    FontSize;
036.             string FontName;
037.          }Regions[MSG_NULL];
038.       }m_Info;
039.       C_Mouse      *m_Mouse;
040. //+------------------------------------------------------------------+
041.       void CreateWindowRAD(int w, int h)
042.          {
043.             m_Info.szObj_Chart = "Chart Trade IDE";
044.             m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit";
045.             ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0);
046.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x);
047.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y);
048.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w);
049.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h);
050.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false);
051.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false);
052.             m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID);
053.          };
054. //+------------------------------------------------------------------+
055.       void AdjustEditabled(C_AdjustTemplate &Template, bool bArg)
056.          {
057.             for (eObjectsIDE c0 = 0; c0 <= MSG_STOP_VALUE; c0++)
058.                if (bArg)
059.                {
060.                   Template.Add(EnumToString(c0), "bgcolor", NULL);
061.                   Template.Add(EnumToString(c0), "fontsz", NULL);
062.                   Template.Add(EnumToString(c0), "fontnm", NULL);
063.                }
064.                else
065.                {
066.                   m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor"));
067.                   m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz"));
068.                   m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm");
069.                }
070.          }
071. //+------------------------------------------------------------------+
072. inline void AdjustTemplate(const bool bFirst = false)
073.          {
074. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade
075.             
076.             C_AdjustTemplate *Template;
077.             
078.             if (bFirst)
079.             {
080.                Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true);
081.                for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
082.                {
083.                   (*Template).Add(EnumToString(c0), "size_x", NULL);
084.                   (*Template).Add(EnumToString(c0), "size_y", NULL);
085.                   (*Template).Add(EnumToString(c0), "pos_x", NULL);
086.                   (*Template).Add(EnumToString(c0), "pos_y", NULL);
087.                }
088.                AdjustEditabled(Template, true);
089.             }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
090.             if (_LastError >= ERR_USER_ERROR_FIRST)
091.             {
092.                delete Template;
093.                
094.                return;
095.             }
096.             m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage);
097.             m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage));
098.             m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage));
099.             (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
100.             (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage));
101.             (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2));
102.             (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2));
103.             (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
104.             (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
105.             if (!(*Template).Execute())
106.             {
107.                delete Template;
108. 
109.                macro_CloseIndicator(C_Terminal::ERR_FileAcess);
110.             };
111.             if (bFirst)
112.             {
113.                for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
114.                {
115.                   m_Info.Regions[c0].x = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_x"));
116.                   m_Info.Regions[c0].y = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_y"));
117.                   m_Info.Regions[c0].w = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_x"));
118.                   m_Info.Regions[c0].h = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_y"));
119.                }
120.                m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x;
121.                AdjustEditabled(Template, false);
122.             };
123.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6));
124.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx));
125.             ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny));
126. 
127.             delete Template;
128.             
129.             ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
130.             ChartRedraw(m_Info.WinHandle);
131. 
132. #undef macro_PointsToFinance
133.          }
134. //+------------------------------------------------------------------+
135.       eObjectsIDE CheckMousePosition(const short x, const short y)
136.          {
137.             int xi, yi, xf, yf;
138.             
139.             for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
140.             {
141.                xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x;
142.                yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y;
143.                xf = xi + m_Info.Regions[c0].w;
144.                yf = yi + m_Info.Regions[c0].h;
145.                if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0;
146.             }
147.             return MSG_NULL;
148.          }
149. //+------------------------------------------------------------------+
150. inline void DeleteObjectEdit(void)
151.          {
152.             ChartRedraw();
153.             ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Editable);
154.          }
155. //+------------------------------------------------------------------+
156.       template <typename T >
157.       void CreateObjectEditable(eObjectsIDE arg, T value)
158.          {
159.             long id = GetInfoTerminal().ID;
160.             
161.             DeleteObjectEdit();
162.             CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0);
163.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3);
164.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3);
165.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w);
166.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h);
167.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor);
168.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER);
169.             ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1);
170.             ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName);
171.             ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value));
172.             ChartRedraw();
173.          }
174. //+------------------------------------------------------------------+
175.       bool RestoreState(void)
176.          {
177.             uCast_Double info;
178.             bool bRet;
179.             C_AdjustTemplate *Template;
180.             
181.             if (bRet = GlobalVariableGet(macro_NameGlobalVariable("POST"), info.dValue))
182.             {
183.                m_Info.x = (short) info._16b[0];
184.                m_Info.y = (short) info._16b[1];
185.                m_Info.minx = (short) info._16b[2];
186.                m_Info.miny = (short) info._16b[3];
187.                Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl");
188.                if (_LastError >= ERR_USER_ERROR_FIRST) bRet = false; else
189.                {
190.                   (*Template).Add("MSG_LEVERAGE_VALUE", "descr", NULL);
191.                   (*Template).Add("MSG_TAKE_VALUE", "descr", NULL);
192.                   (*Template).Add("MSG_STOP_VALUE", "descr", NULL);
193.                   (*Template).Add("MSG_DAY_TRADE", "state", NULL);
194.                   (*Template).Add("MSG_MAX_MIN", "state", NULL);
195.                   if (!(*Template).Execute()) bRet = false; else
196.                   {
197.                      m_Info.IsDayTrade  = (bool) StringToInteger((*Template).Get("MSG_DAY_TRADE", "state")) == 1;
198.                      m_Info.IsMaximized = (bool) StringToInteger((*Template).Get("MSG_MAX_MIN", "state")) == 1;
199.                      m_Info.Leverage    = (short)StringToInteger((*Template).Get("MSG_LEVERAGE_VALUE", "descr"));
200.                      m_Info.FinanceTake = (double) StringToDouble((*Template).Get("MSG_TAKE_VALUE", "descr"));
201.                      m_Info.FinanceStop = (double) StringToDouble((*Template).Get("MSG_STOP_VALUE", "descr"));
202.                   }
203.                };               
204.                delete Template;
205.             };
206.             
207.             GlobalVariablesDeleteAll(macro_NameGlobalVariable(""));
208.             
209.             return bRet;
210.          }
211. //+------------------------------------------------------------------+
212.    public   :
213. //+------------------------------------------------------------------+
214.       C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const short Leverage, const double FinanceTake, const double FinanceStop)
215.          :C_Terminal(0)
216.          {
217.             m_Mouse = MousePtr;
218.             m_Info.IsSaveState = false;
219.             if (!IndicatorCheckPass(szShortName)) return;
220.             if (!RestoreState())
221.             {
222.                m_Info.Leverage    = Leverage;
223.                m_Info.IsDayTrade  = true;
224.                m_Info.FinanceTake = FinanceTake;
225.                m_Info.FinanceStop = FinanceStop;
226.                m_Info.IsMaximized = true;
227.                m_Info.minx = m_Info.x = 115;
228.                m_Info.miny = m_Info.y = 64;
229.             }
230.             CreateWindowRAD(170, 210);
231.             AdjustTemplate(true);
232.          }
233. //+------------------------------------------------------------------+
234.       ~C_ChartFloatingRAD()
235.          {
236.             ChartRedraw();
237.             ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart);
238.             if (!m_Info.IsSaveState)
239.                FileDelete(m_Info.szFileNameTemplate);
240.                         
241.             delete m_Mouse;
242.          }
243. //+------------------------------------------------------------------+
244.       void SaveState(void)
245.          {
246. #define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B);
247.             
248.             uCast_Double info;
249.             
250.             info._16b[0] = m_Info.x;
251.             info._16b[1] = m_Info.y;
252.             info._16b[2] = m_Info.minx;
253.             info._16b[3] = m_Info.miny;
254.             macro_GlobalVariable(macro_NameGlobalVariable("POST"), info.dValue);
255.             m_Info.IsSaveState = true;
256.             
257. #undef macro_GlobalVariable
258.          }
259. //+------------------------------------------------------------------+
260.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
261.          {

.... The internal code of this procedure will be seen in the next article

386.          }
387. //+------------------------------------------------------------------+
388. };
389. //+------------------------------------------------------------------+
390. #undef macro_NameGlobalVariable
391. #undef macro_CloseIndicator
392. //+------------------------------------------------------------------+

Source code of the file C_ChartFloatingRAD.mqh

This version of the code already has the unnecessary lines removed. It also includes the necessary updates to ensure everything works as expected. Since many of you probably haven't seen the original version, I encourage you to pay close attention to the explanations in this article to understand not just how the code works, but especially how it enables the Chart Trade to function and appear on the chart. At first glance, you might not see any of the visual elements you would expect from Chart Trade. So how is this possible? How can code that appears to contain virtually no graphical elements display graphics on the chart? What kind of magic is going on here?

Well, it's not magic. It's programming and clever use of various techniques that, together, allow the Chart Trade to work as expected. Moreover, this makes the Chart Trade work reliably and securely.

In the original version, some things were publicly exposed, such as structures and an enumeration found on line 17. However, care was taken to ensure no variables leaked outside the code. So encapsulation was already a key design principle. But why are those public elements no longer visible? That's because the idea now is to establish a protocol and use it without needing the C_ChartFloatingRAD class to decode the data. We will talk about this later in our article.

Everything we need in terms of variables is now declared within the structure starting at line 18. Pay close attention to the types being used. For the most part, we're not using 32-bit types, or more specifically, INT types. Why? Even variables meant for screen coordinates use 16-bit types (SHORT group).

Unlike the common practice of defaulting to INT, I see no need for that here. In the case of screen resolution, 16 bits is more than enough for modern technologies. A max of 65,535 is more than sufficient for plotting on a 4K screen. Frankly, this would even handle 8K displays. So there's no advantage in using INT here. Besides, using SHORT helps with data compression. We will see this later.

Let's now move on. On line 31, you'll see another structure defined within the first. This internal structure holds the relative positions of the graphical elements. These are the elements that users interact with in the Chart Trade interface.

There are two macros defined at the beginning of the code. Don't worry about them for now, just be aware of their location. We'll use them later. One of them, defined on line 8, may seem a bit confusing at first, but you'll understand its purpose shortly.

Anyway, here's the core of it. On line 14, we privately inherit from the C_Terminal class to expand its functionality and build the Chart Trade. Between lines 20 and 39, we declare the private internal variables of the class. Now we're ready to begin reviewing the code. To make things simple, we will analyze them function by function as they appear in the code. We start with the CreateWindowRAD procedure, declared on line 41.

This procedure is called only once from line 230. Nowhere else. Its role is very simple: it creates an OBJ_CHART object on the chart. That's it. But why add this OBJ_CHART object? By doing so, we don't need to manually create all the other graphical objects. For those used to drawing on charts via code, this might seem confusing, but I have already explained this in previous articles. At the end of this article, I'll include links to those resources so you can study them in more depth. They explain exactly why this approach is taken. If this article doesn't fully clarify the topic for you, those references will be a valuable aid.

The critical part for our current task is line 52. There, we store the ID returned by MetaTrader 5 once the OBJ_CHART object is created. We need this ID as it will be important later. Now we can move to the next procedure: AdjustEditabled, starting on line 55. What does this procedure do? As strange as it may sound, its purpose is to adjust and capture the status of the objects that will appear within the OBJ_CHART. But how does it work? To understand, we need to take a closer look at how the AdjustTemplate procedure works because that's where AdjustEditabled is used. Let's examine what happens between lines 72 and 133. This is one of the most complex parts of the class, and where the "magic" really happens.

On line 74, we define a macro used only within this procedure. Notice that at the end of the procedure, in line 132, the macro is undefined, so it can't be used elsewhere. On line 78, we check what action is needed. If we are building the Chart Trade, this condition will be true, and a whole series of steps will follow. If the Chart Trade already exists, we'll execute line 89 instead. Here, you may revisit the previous article to better understand the type of work done in the template, because either line 80 or 89 will initiate template creation. Again, this depends on the result of checks on line 78.

Now comes the first tricky part. If we are creating the template, the loop on line 81 prepares to locate the objects. But what are these objects? These are objects that are added to Chart Trade. Note: we don't yet know where these objects are, but we do know how many, thanks to the enumeration on line 17. On line 88, we call AdjustEditabled again, ensuring the check on line 58 passes. This step adds more data from the template.

If the C_AdjustTemplate class successfully processes this, line 90 lets the code continue. If it fails, the execution returns to the calling code, as shown in the previous article. But take a breath, we have actually done nothing significant yet. All we've done through line 90 is ask C_AdjustTemplate to open the template file. Nothing more.

Between lines 96 and 98, we adjust some display values. Then, between lines 99 and 104, we prepare data for processing in line 105. That's when the magic truly happens. To understand it, review the Execute function of the C_AdjustTemplate class in the prior article. What we're doing here is modifying values originally stored in the template file. But we are not changing the original - we are working on a copy. That's why the check on line 78 is essential. It tells us which file to modify in line 105.

I'm not sure if you have fully understood what's happening - but let me try to explain. Everything done from line 78 to 104 prepares data for C_AdjustTemplate to edit the file containing Chart Trade. Every element you later see in the Chart Trade is defined between lines 99 and 104. Strange as it may seem, it works.

So here's my advice: study this section of the code closely to really understand how it operates. Every object is created and managed using this same technique.

Now, let's assume you've understood everything up to line 105. If that line succeeds, the code continues. If it fails, something quite unusual happens. Do pay close attention to what comes next.

If the call on line 105 fails, we need to destroy the template and close the Chart Trade. Destroying the template is simple: line 107 handles that. No mystery there. However, that doesn't remove the indicator from the chart. To do that, we need to call the OnDeInit procedure. And now comes the next complex part. Let's look at the OnDeInit code snippet below:

41. void OnDeinit(const int reason)
42. {
43.    switch (reason)
44.    {
45.       case REASON_INITFAILED:
46.          ChartIndicatorDelete(ChartID(), 0, def_ShortName);
47.          break;
48.       case REASON_CHARTCHANGE:
49.          (*chart).SaveState();
50.          break;
51.    }
52. 
53.    delete chart;
54. }

Chart Trade indicator code snippet

This snippet is here solely to help explain the process. So let's clarify the following: when line 109 is executed, control will jump into the macro code. This macro can be seen at the beginning of the code, starting on line 8. When in this macro line 9 is executed, the contents found in line 46 of the snippet will be executed.

At the same time, line 53 of the excerpt will also be executed, which ensures that the Chart Trade indicator is removed. However, the code will then return, and line 10 of the macro will be executed. This line ensures that an error flag is set, which prevents the code from continuing any further. Then, on line 11, we return to the caller, regardless of who called the AdjustTemplate procedure. If the template modification cannot be successfully executed, the indicator is removed without any additional issues. This is one of the more complex parts of the system. But we're not done yet, as there's still more to cover.

Now, let's suppose that the template modification succeeds. In this case, we move to a new check on line 111. This check captures the positions of the clickable objects. This type of action is only performed during template creation. The loop beginning on line 113 handles this task: it captures the positions of the clickable objects, and lines 120 and 121 complement this process.

Next, we position the OBJ_CHART object on the chart. This happens between lines 123 and 125. Line 127 concludes the template manipulation, which allows line 129 to assign the appropriate template to OBJ_CHART. Then line 130 forces the object content to update. With this, the entire structure of the OBJ_CHART object is finalized, and all components are displayed, creating the Chart Trade interface.

If you're still unclear on how the template gets updated in such a way that you don't need to manually code the Chart Trade interface, go back and review the entire AdjustTemplate procedure. This is the core process that configures and displays the Chart Trade. Every object that appears in the Chart Trade is created, positioned, and adjusted within this procedure.

There's no point in digging through the code looking for explicit object declarations, such as buttons, icons, images, or anything else, because they don't exist in the code. What the code does is locate existing objects and make them accessible to the user. Don't try to understand this as a conventional object creation pattern. What I'm doing here is exactly the opposite: I first design the desired appearance, and only then do I instruct the code to make that concept functional. Without understanding this idea, you'll likely find yourself completely lost.

This system is so sophisticated that you can change the layout, structure, or even fonts of the objects without writing a single line of code. All you need to do is open MetaTrader 5, load the Chart Trade template, and modify how it's visually presented on the chart. The rest is handled entirely by the AdjustTemplate procedure.

But of course, there's always something to complicate things. To support this RAD (Rapid Application Development) concept, we need a few additional procedures. The next one we'll look at is the CheckMousePosition function, located on line 135. This function is very simple. It checks whether the mouse pointer is currently within any clickable or selectable object. If it is, line 145 returns the corresponding object. If not, line 147 reports that the mouse is not over any interactive element.

Following that, we have two procedures that support a unique case. This is something the AdjustTemplate procedure can't handle on its own with the required quality. This case involves the OBJ_EDIT object, which allows the user to input arbitrary text.

The procedure on line 150 is responsible for removing any OBJ_EDIT objects that may have been created. Meanwhile, the procedure defined on line 156 is responsible for creating a single OBJ_EDIT object that enables the user to enter a value into the Chart Trade interface.

The declaration of the CreateObjectEditable procedure may seem a bit strange, but there's nothing particularly special about it. It introduces an overload to allow line 171 to adapt the OBJ_EDIT object to match the variable type being edited. For example, if the variable is of type double, the value will display with two decimal places. Otherwise, the value is treated as a string. In this case, the displayed text will be exactly as constructed by the caller.

There's nothing really difficult here, at least not for anyone familiar with MQL5 programming. Everything done is well within the scope of general MQL5 knowledge, even for those with limited experience working with graphical objects.

Now let's move on to the next function: RestoreState, declared on line 175 and implemented through line 210. This procedure doesn't operate alone - it requires support from the SaveState procedure, which starts on line 244. Both of these functions operate outside conventional rules. They work together to temporarily store main information. This includes the last known position of the Chart Trade interface. More precisely, they store the last known location of the OBJ_CHART object before the chart was refreshed.

To store this information, we use global terminal variables. Note that we use only a single global variable to hold the values. However, I want you to pay attention to another important detail In line 187, we use the C_AdjustTemplate class again. Why? Are we modifying the template again? The answer is no. This time, we are using the template in a different way.

Remember that during template creation, back in the AdjustTemplate procedure starting at line 72, I said we could add or remove items from the template? That's exactly what we do here. We use the existing, modified template values to restore data in the Chart Trade. Why is this necessary? Doesn't the OBJ_CHART object load the template to build the Chart Trade interface? Yes, it does. But we restore this data to accommodate possible user interactions with clickable objects, especially the market Buy and Sell buttons. These buttons trigger events that will be explained later. To avoid unnecessary delays or missing data when those events occur, we preemptively restore the necessary values whenever the Chart Trade is restarted.

To correctly restore this information from the template, we first open it - this is done on line 187. If successful, the check on line 188 allows lines 190 through 194 to run. This adds both the object names and the corresponding value positions to be retrieved. Then, in line 195, we attempt to execute the conversion. Since we're not modifying or adding anything to the template, this line simply retrieves the requested data. If this execution is successful, lines 197 to 201 restore the necessary private variables of the C_ChartFloatingRAD class. We don't need to restore everything - just the key values required for handling future events. Finally, line 204 closes the template, and line 207 deletes the temporary global terminal variables.

It's important to understand that these variables have an extremely short lifespan - typically less than a second. They are only used to temporarily store the chart coordinates where OBJ_CHART was located before a timeframe change.

This article is already quite dense with information. However, before we wrap up, let's quickly go over three more procedures. They are simple and easy to understand.

The procedure on line 214 is the class constructor. It's mostly self-explanatory, as it leverages everything discussed in this article. Line 218 flags whether the current state will be saved. By default, nothing is saved. Line 219 attempts to create the indicator. If it fails, control returns to the caller, and the indicator is terminated. On line 220, we try to restore the previous state. If that fails, the user's input values are used instead. Line 230 creates the OBJ_CHART object, and line 231 performs final adjustments.

The procedure starting at line 234 is the class destructor. It is even simpler. Line 237 removes any object associated with OBJ_CHART. This is a crucial step to avoid cluttering the chart with unnecessary remnants. Line 238 checks whether the current state should be saved. If not, line 239 deletes the template file from disk. This means the RestoreState function (lines 175–210) won't work, since the check on line 188 will fail, and the values will default to the last ones provided by the user.

Then we have the SaveState function starting at line 244. It uses an internal macro defined on line 246 and removed on line 257. This macro isn't strictly necessary, but since the global variable name is used twice (once to temporarily create it and once to store the value), using a macro makes the code cleaner. The key line here is 255 - it ensures that the check on line 238 fails, which preserves the template file.


Final Thoughts

Although I haven't covered the DispatchMessage procedure declared on line 260 and implemented through line 386, I won't be explaining it in this article. The reason is simple: it's far too complex to be discussed in this limited space. It's better understood when seen in action alongside another program. That's because this is the procedure responsible for generating the events that allow the user to interact with Chart Trade to open or close market positions.

This topic deserves a much more detailed and thorough explanation. And to ensure that you stay motivated to read the next article, I've deliberately withheld the internal details of this function. This part will be discussed in full in the next article. For now, save the code for the C_ChartFloatingRAD class in a separate file and study it thoroughly as it's already functional. And stay tuned for the upcoming article, where I'll walk through the code between lines 260 and 386.

I'll see you in the next article. Until then, you'll find references to RAD programming in MQL5 below. These are articles I've written in the past, explaining the concepts used to construct Chart Trade as demonstrated here. We don't explicitly declare objects, and yet we’re able to display and interact with them on the chart. But if you open the Objects window using the shortcut CTRL + B, you'll see only a single object listed.

The attachment contains everything you need to see the indicator in action and interact with it just as shown in the accompanying video below.



Links to RAD articles in MQL5

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

Attached files |
Anexo.zip (163.48 KB)
Features of Custom Indicators Creation Features of Custom Indicators Creation
Creation of Custom Indicators in the MetaTrader trading system has a number of features.
Market Profile indicator Market Profile indicator
In this article, we will consider Market Profile indicator. We will find out what lies behind this name, try to understand its operation principles and have a look at its terminal version (MarketProfile).
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Price Action Analysis Toolkit Development (Part 31): Python Candlestick Recognition Engine (I) — Manual Detection Price Action Analysis Toolkit Development (Part 31): Python Candlestick Recognition Engine (I) — Manual Detection
Candlestick patterns are fundamental to price-action trading, offering valuable insights into potential market reversals or continuations. Envision a reliable tool that continuously monitors each new price bar, identifies key formations such as engulfing patterns, hammers, dojis, and stars, and promptly notifies you when a significant trading setup is detected. This is precisely the functionality we have developed. Whether you are new to trading or an experienced professional, this system provides real-time alerts for candlestick patterns, enabling you to focus on executing trades with greater confidence and efficiency. Continue reading to learn how it operates and how it can enhance your trading strategy.