Русский 中文 Español Deutsch 日本語 Português
preview
Market Simulation (Part 03): A Matter of Performance

Market Simulation (Part 03): A Matter of Performance

MetaTrader 5Examples |
1 418 0
Daniel Jose
Daniel Jose

Introduction

In the previous article, Market Simulation (Part 02): Cross Orders (II), I demonstrated how to maintain control over which asset or contract we were observing in the Chart Trade. This applies when using a system where we do not access the traded contract directly. Instead, we use another asset to perform such operations. That asset is, in fact, the historical data of the contract, and the reasons for doing so were explained in the previous article.

That said, although we are now at a much more advanced stage in developing the entire system, it is always necessary to ensure that things remain within an appropriate level of security, reliability, and performance. Achieving this is by no means a simple task. On the contrary, it is far from simple. Sooner or later, we inevitably encounter some type of failure or problem. It is at these moments that a perfect understanding of how the entire system is implemented becomes crucial. If you do not know or understand what each part is actually doing, you may find yourself at a dead end, where things degrade very quickly.

For several weeks, we have been developing some of the applications we will actually need. However, when I began implementing a new application, which will be introduced soon, problems started to arise. The system performance dropped significantly compared to what had been observed until then. Since I do not intend to build the system monolithically, that is, as a single block, it became necessary to analyze and identify the causes of this performance degradation.

While studying the flowchart, I noticed some gaps. These gaps need to be closed because sooner or later they will generate problems. Therefore, resolving these issues and preventing them from recurring is also part of the final application development. In addition to these details, which will be explained throughout the article, there is a minor flaw that can be observed when using the mouse indicator. This flaw had gone unnoticed, but upon closer examination, it was detected and corrected, and you, dear reader, will be able to see exactly what it entails.

This kind of detail, often omitted or unexplained by others, can lead aspiring professional programmers to erroneously believe that programmers never make mistakes, or that code is born flawless and develops without errors. In reality, no code - no matter how carefully planned - is free from errors. My hope is that this article, beyond being explanatory, serves to demonstrate the following: code is never truly finished. There is always something to improve or correct.

With that said, let us proceed to the article itself, beginning with a new topic.


Improving Encapsulation

One of the worst problems that can affect software under development is information leakage. This leakage can take many forms. Here, however, the issue is very specific. The code is being designed for extensive use of classes and, consequently, object-oriented programming. Leakage occurs when a class - or an application using a particular class - gains access to something it should not. Such flaws usually result from poor encapsulation or from functions or procedures being present in a class where they do not belong. In our particular case, there is a procedure that should not be accessible by other code, except for the specific code to which it belongs. However, since I had not paid attention to this issue until now, the flaw remained.

The flaw in question is the SetBuffer function in the C_Mouse class. But why is this procedure considered a flaw? Is there something inherently wrong with it being in the C_Mouse class? I would not say it is wrong. However, upon closer inspection, I realized it is not entirely correct. The problem is not that another program (be it an indicator or an Expert Advisor) can write to the mouse indicator buffer. That does not happen due to security features inherent to MQL5. The issue is that it makes no sense for this procedure to exist in the C_Mouse class when the only process that uses it is the mouse indicator itself. By removing this procedure from the C_Mouse class, we improve its encapsulation while ensuring that only the mouse indicator can modify its buffer.

Removing the procedure from the class requires that it continue functioning as before. This guarantees compatibility with any existing or previously developed code that relies on data from the mouse indicator. Before showing the updated C_Mouse class (which underwent other changes besides this one), let us review the mouse indicator code. The complete code is shown below:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "This is an indicator for graphical studies using the mouse."
04. #property description "This is an integral part of the Replay / Simulator system."
05. #property description "However it can be used in the real market."
06. #property version "1.82"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/pt/articles/12580"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. //+------------------------------------------------------------------+
13. double GL_PriceClose;
14. datetime GL_TimeAdjust;
15. //+------------------------------------------------------------------+
16. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
17. //+------------------------------------------------------------------+
18. C_Study *Study       = NULL;
19. //+------------------------------------------------------------------+
20. input color user01   = clrBlack;                   //Price Line
21. input color user02   = clrPaleGreen;               //Positive Study
22. input color user03   = clrLightCoral;              //Negative Study
23. //+------------------------------------------------------------------+
24. C_Study::eStatusMarket m_Status;
25. int m_posBuff = 0;
26. double m_Buff[];
27. //+------------------------------------------------------------------+
28. int OnInit()
29. {
30.    Study = new C_Study(0, "Indicator Mouse Study", user01, user02, user03);
31.    if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED;
32.    MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
33.    OnBookEvent((*Study).GetInfoTerminal().szSymbol);
34.    m_Status = C_Study::eCloseMarket;
35.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
36.    ArrayInitialize(m_Buff, EMPTY_VALUE);
37.    
38.    return INIT_SUCCEEDED;
39. }
40. //+------------------------------------------------------------------+
41. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 
42.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
43.                 const long& volume[], const int& spread[]) 
44. {
45.    GL_PriceClose = close[rates_total - 1];
46.    if (_Symbol == def_SymbolReplay)
47.       GL_TimeAdjust = spread[rates_total - 1] & (~def_MaskTimeService);
48.    m_posBuff = rates_total;
49.    (*Study).Update(m_Status);   
50.    
51.    return rates_total;
52. }
53. //+------------------------------------------------------------------+
54. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
55. {
56.     uCast_Double info;
57.     C_Mouse::st_Mouse sMouse;
58.     
59.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
60.    sMouse = (*Study).GetInfoMouse();            
61.    info._8b[0] = (uchar)(sMouse.ExecStudy == C_Mouse::eStudyNull ? sMouse.ButtonStatus : 0);
62.    info._16b[1] = (ushort)sMouse.Position.X_Graphics;
63.    info._16b[2] = (ushort)sMouse.Position.Y_Graphics;
64.    if (m_posBuff > 0) m_Buff[m_posBuff - 1] = info.dValue;
65. 
66.    ChartRedraw((*Study).GetInfoTerminal().ID);
67. }
68. //+------------------------------------------------------------------+
69. void OnBookEvent(const string &symbol)
70. {
71.    MqlBookInfo book[];
72.    C_Study::eStatusMarket loc = m_Status;
73.    
74.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
75.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
76.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : (symbol == def_SymbolReplay ? C_Study::eInReplay : C_Study::eInTrading));
77.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
78.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
79.    if (loc != m_Status) (*Study).Update(m_Status);
80. }
81. //+------------------------------------------------------------------+
82. void OnDeinit(const int reason)
83. {
84.    MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
85. 
86.    delete Study;
87. }
88. //+------------------------------------------------------------------+

Mouse Pointer source code

Notice that there are almost no changes compared to the original code. You can refer to the original code in previous articles. However, if you have been following the code closely, you will notice a difference in the OnChartEvent procedure. Previously, the same operation was handled within the C_Mouse class in the SetBuffer function. Now that this procedure has been removed, we ensure that the buffer is still written correctly in OnChartEvent.

Why not perform this operation in the OnCalculate function, which would seem more natural? The reason is that we are dealing with mouse-related events. OnCalculate is designed for price-related calculations. Since the purpose of the mouse indicator is to provide mouse-related data, we must use a more appropriate function.

Thus, almost the same code that existed in SetBuffer is now between lines 61 and 64. However, at line 64, an additional test must be executed that was not previously performed. This test addresses peculiar failures observed during experiments in the replay/simulator system related to the mouse indicator. This test is run on line 64 to avoid these failures. Implementing this test eliminated those failures, resolving another issue at the right time.

In addition to this improvement to the mouse indicator code, there are two other changes I want to highlight. Although they are not yet in active use, they are already being implemented because I am integrating other applications into the replay/simulator system. I may discuss these changes in future articles, but even if I do not, having them in place prevents the need to remove them repeatedly. They may prove useful in certain situations, and if you aim to implement something similar, you will already have access to them.

The first is the header file containing macros.mqh. The complete file is shown below.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define macroRemoveSec(A)            (A - (A % 60))
05. #define macroGetDate(A)              (A - (A % 86400))
06. #define macroGetSec(A)               (A - (A - (A % 60)))
07. #define macroGetTime(A)              (A % 86400)
08. #define macroGetMin(A)               (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
09. #define macroGetHour(A)              (A - (A - ((A % 86400) - (A % 3600))))
10. #define macroHourBiggerOrEqual(A, B) ((A * 3600) < (B - (B - ((B % 86400) - (B % 3600)))))
11. #define macroMinusMinutes(A, B)      (B - ((A * 60) + (B % 60)))
12. #define macroMinusHours(A, B)        (B - (A * 3600))
13. #define macroAddHours(A, B)          (B + (A * 3600))
14. #define macroAddMin(A, B)            (B + (A * 60))
15. #define macroSetHours(A, B)          ((A * 3600) + (B - ((B % 86400))))
16. #define macroSetMin(A, B)            ((A * 60) + (B - (B % 3600)))
17. #define macroSetTime(A, B, C)        ((A * 3600) + (B * 60) + (C - (C % 86400)))
18. //+------------------------------------------------------------------+
19. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16)))
20. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
21. //+------------------------------------------------------------------+

Header file Macros.mqh

Although much of this file is focused on manipulating time - whether using the datetime type or ulong to represent dates and times - these functions are extremely valuable for a wide range of tasks related to such manipulations. This is because performing a calculation directly is faster for the processor than calling an MQL5 library function to achieve the same result. You might not immediately notice the usefulness of these calculations, but in any case, the macros.mqh file will now contain this content.

The other header file that also underwent changes is defines.mqh. However, the change was minimal: a single line was added. Even so, the file in its entirety 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.                   };
46. //+------------------------------------------------------------------+

Header file Defines.mqh

The line in question is line 30. Adding it at the beginning of the enumeration changes everything. This event - the so-called Tic-Tac - is a synchronization event. Currently, it is not actually used in the replay/simulator system code. But when synchronization across multiple components becomes necessary, this event will be in high demand. Its addition is due to some applications I am developing for personal use, as mentioned earlier, where I am integrating all my projects into the replay/simulator system.

Thus, the Tic-Tac event is one of those aspects that, at present, you, dear reader, may not fully understand as part of the replay/simulator code. Do not worry; perhaps in the future, I will show applications that make use of this event.

With that, we conclude this section and move on to the next, where the main focus is preventing system performance degradation. This is an issue that has already begun to manifest. To separate topics clearly, let us proceed to a new section.


Reducing Performance Degradation

For a long time, the system maintained adequate performance. However, when I entered a new phase, making more extensive use of certain features, a general performance degradation was observed in the replay/simulator system. This occurs even when the programs developed for it are used on a live or demo account. The reasons for this degradation are quite specific, and I will explain the cause and propose a solution that is suitable, at least for this initial stage.

The main culprit responsible for the overall performance drop is the mouse indicator. Why? It is not that the mouse indicator consumes more resources than expected. Rather, when the buffer of the indicator is read more intensively, the system starts to slow down.

This is caused by a procedure within the C_Mouse class. Specifically, when reading the indicator buffer, small delays begin to accumulate. These delays result from the system converting graphical coordinates to price-time coordinates. If this conversion happens too frequently, there is a perceptible lag in the position of the price line associated with the mouse indicator.

The solution is to isolate this effect. This can be done either by reading the buffer less frequently or by using another technique. Since the entire system is graphical, we cannot eliminate the use of the mouse. However, there is a solution without resorting to extreme measures.

The key is that the mouse indicator must differentiate between periods when a study is being conducted and when it is not. This is the fundamental reason for its creation. When a study is in progress, any click must be ignored by the applications on the chart.

To determine what the mouse is doing - whether it is active or in study mode - we use a test. It is executed via the CheckClick function. If the function returns true, the mouse position may be used. If it returns false, the mouse is in study mode. In this case, any position should be ignored by the application accessing the mouse, provided it is aware of the mouse status.

This requires the mouse indicator to be present on the chart. Resolving all these issues required several changes. To avoid making this discussion overly long and tedious (since most changes involve moving functionality from one class to another) I will not delve into the minute details.

What you need to understand is that previously, all the work was performed within the C_Mouse class so that any application using the mouse indicator could access the necessary data. Now, this work is divided between C_Mouse and C_Terminal. However, without the mouse indicator on the chart, applications dependent on it will not function fully. The new class hierarchy, reflecting applications developed to date, is shown in the image below.

Image

This image illustrates the updated class structure. So, this is our basic structure now. Each rectangle represents a different application. Even though C_ChartFloatingRAD inherits from C_Mouse, the Chart Trade indicator will not function without the mouse indicator present on the chart.

This may seem counterintuitive: the fact that C_ChartFloatingRAD inherits C_Mouse might suggest that Chart Trade would work independently. However, due to how these indicators were designed, Chart Trade depends on the mouse indicator. If it is absent, the user cannot access Chart Trade.

This behavior was intentional and deemed appropriate. Both keyboard and mouse interactions are linked to C_Terminal and C_Mouse. These classes were designed to minimize overall performance degradation. Nonetheless, if necessary, the mouse indicator's buffer contains data that can be accessed by the Chart Trade indicator - or any other application.

The idea is that any component within the graphical terminal should access C_Mouse data as quickly as possible. If this data is needed by another application or service, the buffer can be read externally, transferring the computational load and preserving overall system performance.

Additionally, several changes were made to reduce the number of function calls. Often, values can be stored in private class variables if they remain constant during the application's lifecycle. Using a variable is preferred over repeatedly executing a function to retrieve the same value.

As a result, many calls to retrieve the chart ID have been replaced by references to a stored variable initialized in the class constructor. This value does not change while the chart is open. Although this saves only a few processor cycles, such optimizations can make a significant difference, especially when the number of function calls is high.

Individually, these changes may not yield a dramatic performance boost, but accumulated, they become significant.

Finally, let us examine how the new code is structured. The purpose of these articles is not to provide source code but to impart knowledge. I will maintain the current approach: provide explanations of each and every change. Since the applications use multiple files linked as shown in the diagram, posting the code directly in the article prevents readers from simply copying and pasting it without understanding how to compile the system.

For beginners, I apologize for this approach. My goal is not to encourage CTRL+C/CTRL+V programming, which often leads to broken code. Instead, I want you to understand the underlying logic. Those with some programming knowledge can access the sources readily. We now begin with the C_Terminal class. The complete new class is shown below:

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

Header file C_Terminal.mqh

Those viewing this code for the first time may think it is large and complex. However, much of it has remained intact. That said, changes were made because it now assists the C_Mouse class in executing its tasks. This is why we now see the CHARTEVENT_MOUSE_MOVE event in the DispatchMessage procedure. Most of the work will be handled here.

Notice that on line 12, we now have a structure to provide mouse data. However, the DecodeMousePosition procedure, found on line 69, will not fill in data related to the mouse buttons. This responsibility now belongs to the C_Mouse class. Consequently, the next item to review is the C_Mouse class itself, which is shown below.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_"
007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0))
008. //+------------------------------------------------------------------+
009. class C_Mouse : public C_Terminal
010. {
011.    public   :
012.       enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
013.       enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
014. //+------------------------------------------------------------------+
015.    protected:
016. //+------------------------------------------------------------------+
017.       void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const
018.          {
019.             if (!m_Mem.IsInitOk) return;
020.             CreateObjectGraphics(szName, OBJ_BUTTON);
021.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_STATE, true);
022.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_BORDER_COLOR, clrBlack);
023.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_COLOR, clrBlack);
024.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_BGCOLOR, backColor);
025.             ObjectSetString(m_Mem.id, szName, OBJPROP_FONT, "Lucida Console");
026.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_FONTSIZE, 10);
027.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
028.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_XDISTANCE, x);
029.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
030.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_XSIZE, w); 
031.             ObjectSetInteger(m_Mem.id, szName, OBJPROP_YSIZE, 18);
032.          }
033. //+------------------------------------------------------------------+
034.    private   :
035.       enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
036.       struct st01
037.       {
038.          st_Mouse Data;
039.          color    corLineH,
040.                   corTrendP,
041.                   corTrendN;
042.          eStudy   Study;
043.       }m_Info;
044.       struct st_Mem
045.       {
046.          bool     CrossHair,
047.                   IsFull,
048.                   IsInitOk;
049.          datetime dt;
050.          string   szShortName,
051.                   szLineH,
052.                   szLineV,
053.                   szLineT,
054.                   szBtnS;
055.          long     id;
056.       }m_Mem;
057. //+------------------------------------------------------------------+
058.       void GetDimensionText(const string szArg, int &w, int &h)
059.          {
060.             TextSetFont("Lucida Console", -100, FW_NORMAL);
061.             TextGetSize(szArg, w, h);
062.             h += 5;
063.             w += 5;
064.          }
065. //+------------------------------------------------------------------+
066.       void CreateStudy(void)
067.          {
068.             if (m_Mem.IsFull)
069.             {
070.                CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH);
071.                CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH);
072.                ObjectSetInteger(m_Mem.id, m_Mem.szLineT, OBJPROP_WIDTH, 2);
073.                CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy);
074.             }
075.             m_Info.Study = eStudyCreate;
076.          }
077. //+------------------------------------------------------------------+
078.       void ExecuteStudy(const double memPrice)
079.          {
080.             double v1 = GetPositionsMouse().Position.Price - memPrice;
081.             int w, h;
082.             
083.             if (!CheckClick(eClickLeft))
084.             {
085.                m_Info.Study = eStudyNull;
086.                ChartSetInteger(m_Mem.id, CHART_MOUSE_SCROLL, true);
087.                if (m_Mem.IsFull)   ObjectsDeleteAll(m_Mem.id, def_MousePrefixName + "T");
088.             }else if (m_Mem.IsFull)
089.             {
090.                string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
091.                                          MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetPositionsMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0));
092.                GetDimensionText(sz1, w, h);
093.                ObjectSetString(m_Mem.id, m_Mem.szBtnS, OBJPROP_TEXT, sz1);                                                
094.                ObjectSetInteger(m_Mem.id, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
095.                ObjectSetInteger(m_Mem.id, m_Mem.szBtnS, OBJPROP_XSIZE, w);
096.                ObjectSetInteger(m_Mem.id, m_Mem.szBtnS, OBJPROP_YSIZE, h);
097.                ObjectSetInteger(m_Mem.id, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetPositionsMouse().Position.X_Adjusted - w);
098.                ObjectSetInteger(m_Mem.id, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));            
099.                ObjectMove(m_Mem.id, m_Mem.szLineT, 1, GetPositionsMouse().Position.dt, GetPositionsMouse().Position.Price);
100.                ObjectSetInteger(m_Mem.id, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetPositionsMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
101.             }
102.             m_Info.Data.ButtonStatus = eKeyNull;
103.          }
104. //+------------------------------------------------------------------+
105.    public   :
106. //+------------------------------------------------------------------+
107.       C_Mouse(const long id, const string szShortName)
108.          :C_Terminal(id)
109.          {
110.             m_Mem.IsInitOk = false;
111.             m_Mem.id = GetInfoTerminal().ID;
112.             m_Mem.szShortName = szShortName;
113.          }
114. //+------------------------------------------------------------------+
115.       C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
116.          :C_Terminal(id)
117.          {
118.             m_Mem.id = GetInfoTerminal().ID;
119.             if (!(m_Mem.IsInitOk = IndicatorCheckPass(m_Mem.szShortName = szShortName))) return;
120.             m_Mem.CrossHair = (bool)ChartGetInteger(m_Mem.id, CHART_CROSSHAIR_TOOL);
121.             ChartSetInteger(m_Mem.id, CHART_EVENT_MOUSE_MOVE, true);
122.             ChartSetInteger(m_Mem.id, CHART_CROSSHAIR_TOOL, false);
123.             ZeroMemory(m_Info);
124.             m_Info.corLineH  = corH;
125.             m_Info.corTrendP = corP;
126.             m_Info.corTrendN = corN;
127.             m_Info.Study = eStudyNull;
128.             if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
129.                CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH);
130.             ChartRedraw(m_Mem.id);
131.          }
132. //+------------------------------------------------------------------+
133.       ~C_Mouse()
134.          {
135.             if (!m_Mem.IsInitOk) return;
136.             ChartSetInteger(m_Mem.id, CHART_EVENT_OBJECT_DELETE, false);
137.             ChartSetInteger(m_Mem.id, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(m_Mem.id, m_Mem.szShortName) != -1);
138.             ChartSetInteger(m_Mem.id, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
139.             ObjectsDeleteAll(m_Mem.id, def_MousePrefixName);
140.          }
141. //+------------------------------------------------------------------+
142. inline bool CheckClick(const eBtnMouse value) 
143.          {
144.             return (m_Info.Data.ButtonStatus & value) == value;
145.          }
146. //+------------------------------------------------------------------+
147. inline const st_Mouse GetInfoMouse(void)
148.          {
149.             if (!m_Mem.IsInitOk)
150.             {
151.                double Buff[];
152.                uCast_Double loc;
153.                int handle = ChartIndicatorGet(m_Mem.id, 0, m_Mem.szShortName);
154. 
155.                ZeroMemory(m_Info.Data);
156.                if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
157.                {
158.                   loc.dValue = Buff[0];
159.                   DecodeMousePosition((int)loc._16b[1], (int)loc._16b[2]);
160.                   m_Info.Data = GetPositionsMouse();
161.                   m_Info.Data.ButtonStatus = loc._8b[0];
162.                }
163.                IndicatorRelease(handle);
164.             }
165.             return m_Info.Data;
166.          }
167. //+------------------------------------------------------------------+*/
168.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
169.          {
170.             int w = 0;
171.             static double memPrice = 0;
172.       
173.             C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
174.             switch (id)
175.             {
176.                case (CHARTEVENT_CUSTOM + evHideMouse):
177.                   if (m_Mem.IsFull)   ObjectSetInteger(m_Mem.id, m_Mem.szLineH, OBJPROP_COLOR, clrNONE);
178.                   break;
179.                case (CHARTEVENT_CUSTOM + evShowMouse):
180.                   if (m_Mem.IsFull) ObjectSetInteger(m_Mem.id, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH);
181.                   break;
182.                case CHARTEVENT_MOUSE_MOVE:
183.                   m_Info.Data = GetPositionsMouse();
184.                   if (m_Mem.IsFull) ObjectMove(m_Mem.id, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price);
185.                   if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(m_Mem.id, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0);
186.                   m_Info.Data.ButtonStatus = (uchar) sparam;
187.                   if (CheckClick(eClickMiddle) && (m_Info.Study != eStudyCreate))
188.                      if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(m_Mem.id, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
189.                   if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
190.                   {
191.                      ChartSetInteger(m_Mem.id, CHART_MOUSE_SCROLL, false);
192.                      if (m_Mem.IsFull)   ObjectMove(m_Mem.id, m_Mem.szLineT, 0, m_Mem.dt = m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price);
193.                      m_Info.Study = eStudyExecute;
194.                   }
195.                   if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
196.                   m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
197.                   break;
198.                case CHARTEVENT_OBJECT_DELETE:
199.                   if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH))
200.                      CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH);
201.                   break;
202.             }
203.          }
204. //+------------------------------------------------------------------+
205. };
206. //+------------------------------------------------------------------+
207. #undef macro_NameObjectStudy
208. //+------------------------------------------------------------------+

Header file C_Mouse.mqh

Although this class may seem unchanged at first glance, it has undergone several modifications. These changes aim to make the use of the mouse indicator more explicit. You can see that some differences exist. I leave it as an exercise for enthusiasts and aspiring professional programmers to compare this code with the previous version of the C_Mouse class. Believe me, it will be quite instructive and enjoyable, revealing many interesting aspects of class inheritance. Here's just a hint to pique your curiosity. Look at line 187 and ask yourself: why, during the creation of a study, am I checking whether a study is already in progress?

Unsurprisingly, the C_Study class code has also been modified. These changes aim solely to prevent a constant value - in this case, the chart ID - from being repeatedly retrieved through a function call. The updated C_Study class code can also be seen in full below.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.          long           id;
025.       }m_Info;
026. //+------------------------------------------------------------------+
027.       void Draw(void)
028.          {
029.             double v1;
030.             
031.             if (m_Info.bvT)
032.             {
033.                ObjectSetInteger(m_Info.id, m_Info.szBtn1, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - 18);
034.                ObjectSetString(m_Info.id, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
035.             }
036.             if (m_Info.bvD)
037.             {
038.                v1 = NormalizeDouble((((GetPositionsMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
039.                ObjectSetInteger(m_Info.id, m_Info.szBtn2, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - 1);
040.                ObjectSetInteger(m_Info.id, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
041.                ObjectSetString(m_Info.id, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
042.             }
043.             if (m_Info.bvP)
044.             {
045.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
046.                ObjectSetInteger(m_Info.id, m_Info.szBtn3, OBJPROP_YDISTANCE, GetPositionsMouse().Position.Y_Adjusted - 1);
047.                ObjectSetInteger(m_Info.id, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
048.                ObjectSetString(m_Info.id, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
049.             }
050.          }
051. //+------------------------------------------------------------------+
052. inline void CreateObjInfo(EnumEvents arg)
053.          {
054.             switch (arg)
055.             {
056.                case evShowBarTime:
057.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
058.                   m_Info.bvT = true;
059.                   break;
060.                case evShowDailyVar:
061.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
062.                   m_Info.bvD = true;
063.                   break;
064.                case evShowPriceVar:
065.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
066.                   m_Info.bvP = true;
067.                   break;
068.             }
069.          }
070. //+------------------------------------------------------------------+
071. inline void RemoveObjInfo(EnumEvents arg)
072.          {
073.             string sz;
074.             
075.             switch (arg)
076.             {
077.                case evHideBarTime:
078.                   sz = m_Info.szBtn1;
079.                   m_Info.bvT = false;
080.                   break;
081.                case evHideDailyVar:
082.                   sz = m_Info.szBtn2;
083.                   m_Info.bvD   = false;
084.                   break;
085.                case evHidePriceVar:
086.                   sz = m_Info.szBtn3;
087.                   m_Info.bvP = false;
088.                   break;
089.             }
090.             ChartSetInteger(m_Info.id, CHART_EVENT_OBJECT_DELETE, false);
091.             ObjectDelete(m_Info.id, sz);
092.             ChartSetInteger(m_Info.id, CHART_EVENT_OBJECT_DELETE, true);
093.          }
094. //+------------------------------------------------------------------+
095.    public   :
096. //+------------------------------------------------------------------+
097.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
098.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
099.          {
100.             ZeroMemory(m_Info);
101.             m_Info.id = GetInfoTerminal().ID;
102.             if (_LastError >= ERR_USER_ERROR_FIRST) return;
103.             m_Info.corP = corP;
104.             m_Info.corN = corN;
105.             CreateObjInfo(evShowBarTime);
106.             CreateObjInfo(evShowDailyVar);
107.             CreateObjInfo(evShowPriceVar);
108.             ResetLastError();
109.          }
110. //+------------------------------------------------------------------+
111.       void Update(const eStatusMarket arg)
112.          {
113.             int i0;
114.             datetime dt;
115.                
116.             if (m_Info.Rate.close == 0)
117.                m_Info.Rate.close = iClose(NULL, PERIOD_D1, ((_Symbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(NULL, PERIOD_D1, 0))) ? 0 : 1));
118.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
119.             {
120.                case eCloseMarket   :
121.                   m_Info.szInfo = "Closed Market";
122.                   break;
123.                case eInReplay      :
124.                case eInTrading     :
125.                   i0 = PeriodSeconds();
126.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
127.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
128.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
129.                   break;
130.                case eAuction      :
131.                   m_Info.szInfo = "Auction";
132.                   break;
133.                default            :
134.                   m_Info.szInfo = "ERROR";
135.             }
136.             Draw();
137.          }
138. //+------------------------------------------------------------------+
139. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
140.          {
141.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
142.             switch (id)
143.             {
144.                case CHARTEVENT_CUSTOM + evHideBarTime:
145.                   RemoveObjInfo(evHideBarTime);
146.                   break;
147.                case CHARTEVENT_CUSTOM + evShowBarTime:
148.                   CreateObjInfo(evShowBarTime);
149.                   break;
150.                case CHARTEVENT_CUSTOM + evHideDailyVar:
151.                   RemoveObjInfo(evHideDailyVar);
152.                   break;
153.                case CHARTEVENT_CUSTOM + evShowDailyVar:
154.                   CreateObjInfo(evShowDailyVar);
155.                   break;
156.                case CHARTEVENT_CUSTOM + evHidePriceVar:
157.                   RemoveObjInfo(evHidePriceVar);
158.                   break;
159.                case CHARTEVENT_CUSTOM + evShowPriceVar:
160.                   CreateObjInfo(evShowPriceVar);
161.                   break;
162.                case CHARTEVENT_MOUSE_MOVE:
163.                   Draw();
164.                   break;
165.             }
166.             ChartRedraw(m_Info.id);
167.          }
168. //+------------------------------------------------------------------+
169. };
170. //+------------------------------------------------------------------+
171. #undef def_ExpansionPrefix
172. #undef def_MousePrefixName
173. //+------------------------------------------------------------------+

Header file C_Study.mqh

If you use all the source code reviewed up to this point, you will be able to create the mouse indicator. However, two more files remain to complete this article. I prefer not to defer them to a future article, as the next article will cover a different topic. This way, everything will be ready, and we avoid revisiting this subject for some time. Let us now examine the final two source files, beginning with the C_ChartFloatingRAD class, shown in full below:

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", m_Init.id, A)
008. #define macro_CloseIndicator(A)   {           \
009.                OnDeinit(REASON_INITFAILED);   \
010.                SetUserError(A);               \
011.                return;                        \
012.                                  }
013. //+------------------------------------------------------------------+
014. class C_ChartFloatingRAD : private C_Mouse
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.       struct st01
040.       {
041.          short     y[2];
042.          bool      bOk;
043.          long      id;
044.       }m_Init;
045. //+------------------------------------------------------------------+
046.       void CreateWindowRAD(int w, int h)
047.          {
048.             m_Info.szObj_Chart = "Chart Trade IDE";
049.             m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit";
050.             ObjectCreate(m_Init.id, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0);
051.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x);
052.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y);
053.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_XSIZE, w);
054.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_YSIZE, h);
055.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false);
056.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false);
057.             m_Info.WinHandle = ObjectGetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_CHART_ID);
058.          };
059. //+------------------------------------------------------------------+
060.       void AdjustEditabled(C_AdjustTemplate &Template, bool bArg)
061.          {
062.             for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_STOP_VALUE; c0++)
063.                if (bArg)
064.                {
065.                   Template.Add(EnumToString(c0), "bgcolor", NULL);
066.                   Template.Add(EnumToString(c0), "fontsz", NULL);
067.                   Template.Add(EnumToString(c0), "fontnm", NULL);
068.                }
069.                else
070.                {
071.                   m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor"));
072.                   m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz"));
073.                   m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm");
074.                }
075.          }
076. //+------------------------------------------------------------------+
077. inline void AdjustTemplate(const bool bFirst = false)
078.          {
079. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade
080.             
081.             C_AdjustTemplate    *Template;
082.             
083.             if (bFirst)
084.             {
085.                Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(m_Init.id) + ".tpl", true);
086.                for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_CLOSE_POSITION; c0++)
087.                {
088.                   (*Template).Add(EnumToString(c0), "size_x", NULL);
089.                   (*Template).Add(EnumToString(c0), "size_y", NULL);
090.                   (*Template).Add(EnumToString(c0), "pos_x", NULL);
091.                   (*Template).Add(EnumToString(c0), "pos_y", NULL);
092.                }
093.                AdjustEditabled(Template, true);
094.             }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
095.             if (_LastError >= ERR_USER_ERROR_FIRST)
096.             {
097.                delete Template;
098.                
099.                return;
100.             }
101.             m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage);
102.             m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage));
103.             m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage));
104.             (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
105.             (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage));
106.             (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2));
107.             (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2));
108.             (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
109.             (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
110.             if (!(*Template).Execute())
111.             {
112.                delete Template;
113. 
114.                macro_CloseIndicator(C_Terminal::ERR_FileAcess);
115.             };
116.             if (bFirst)
117.             {
118.                for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_CLOSE_POSITION; c0++)
119.                {
120.                   m_Info.Regions[c0].x = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_x"));
121.                   m_Info.Regions[c0].y = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_y"));
122.                   m_Info.Regions[c0].w = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_x"));
123.                   m_Info.Regions[c0].h = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_y"));
124.                }
125.                m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x;
126.                AdjustEditabled(Template, false);
127.             };
128.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? m_Init.y[m_Init.bOk] : m_Info.Regions[MSG_TITLE_IDE].h + 6));
129.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx));
130.             ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny));
131. 
132.             delete Template;
133.             
134.             ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
135.             ChartRedraw(m_Info.WinHandle);
136. 
137. #undef macro_PointsToFinance
138.          }
139. //+------------------------------------------------------------------+
140.       eObjectsIDE CheckMousePosition(short &x, short &y)
141.          {
142.             int xi, yi, xf, yf;
143.             st_Mouse loc;
144.             
145.             loc = GetPositionsMouse();
146.             x = loc.Position.X_Graphics;
147.             y = loc.Position.Y_Graphics;
148.             for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_CLOSE_POSITION; c0++)
149.             {
150.                xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x;
151.                yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y;
152.                xf = xi + m_Info.Regions[c0].w;
153.                yf = yi + m_Info.Regions[c0].h;
154.                if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0;
155.             }
156.             return MSG_NULL;
157.          }
158. //+------------------------------------------------------------------+
159. inline void DeleteObjectEdit(void)
160.          {
161.             ChartRedraw();
162.             ObjectsDeleteAll(m_Init.id, m_Info.szObj_Editable);
163.          }
164. //+------------------------------------------------------------------+
165.       template <typename T >
166.       void CreateObjectEditable(eObjectsIDE arg, T value)
167.          {
168.             DeleteObjectEdit();
169.             CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0);
170.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3);
171.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3);
172.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w);
173.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h);
174.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor);
175.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER);
176.             ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1);
177.             ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName);
178.             ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value));
179.             ChartRedraw();
180.          }
181. //+------------------------------------------------------------------+
182.       bool RestoreState(void)
183.          {
184.             uCast_Double info;
185.             bool bRet;
186.             C_AdjustTemplate *Template;
187.             
188.             if (bRet = GlobalVariableGet(macro_NameGlobalVariable("POST"), info.dValue))
189.             {
190.                m_Info.x = (short) info._16b[0];
191.                m_Info.y = (short) info._16b[1];
192.                m_Info.minx = (short) info._16b[2];
193.                m_Info.miny = (short) info._16b[3];
194.                Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(m_Init.id) + ".tpl");
195.                if (_LastError >= ERR_USER_ERROR_FIRST) bRet = false; else
196.                {
197.                   (*Template).Add("MSG_LEVERAGE_VALUE", "descr", NULL);
198.                   (*Template).Add("MSG_TAKE_VALUE", "descr", NULL);
199.                   (*Template).Add("MSG_STOP_VALUE", "descr", NULL);
200.                   (*Template).Add("MSG_DAY_TRADE", "state", NULL);
201.                   (*Template).Add("MSG_MAX_MIN", "state", NULL);
202.                   if (!(*Template).Execute()) bRet = false; else
203.                   {
204.                      m_Info.IsDayTrade = (bool) StringToInteger((*Template).Get("MSG_DAY_TRADE", "state")) == 1;
205.                      m_Info.IsMaximized = (bool) StringToInteger((*Template).Get("MSG_MAX_MIN", "state")) == 1;
206.                      m_Info.Leverage = (short)StringToInteger((*Template).Get("MSG_LEVERAGE_VALUE", "descr"));
207.                      m_Info.FinanceTake = (double) StringToDouble((*Template).Get("MSG_TAKE_VALUE", "descr"));
208.                      m_Info.FinanceStop = (double) StringToDouble((*Template).Get("MSG_STOP_VALUE", "descr"));
209.                   }
210.                };               
211.                delete Template;
212.             };
213.             
214.             GlobalVariablesDeleteAll(macro_NameGlobalVariable(""));
215.             
216.             return bRet;
217.          }
218. //+------------------------------------------------------------------+
219.    public   :
220. //+------------------------------------------------------------------+
221.       C_ChartFloatingRAD(string szShortName, const short Leverage, const double FinanceTake, const double FinanceStop)
222.          :C_Mouse(0, "")
223.          {
224.             m_Init.id = GetInfoTerminal().ID;
225.             m_Info.IsSaveState = false;
226.             if (!IndicatorCheckPass(szShortName)) return;
227.             if (!RestoreState())
228.             {
229.                m_Info.Leverage = Leverage;
230.                m_Info.IsDayTrade = true;
231.                m_Info.FinanceTake = FinanceTake;
232.                m_Info.FinanceStop = FinanceStop;
233.                m_Info.IsMaximized = true;
234.                m_Info.minx = m_Info.x = 115;
235.                m_Info.miny = m_Info.y = 64;
236.             }
237.             m_Init.y[false] = 150;
238.             m_Init.y[true] = 210;
239.             CreateWindowRAD(170, m_Init.y[m_Init.bOk = false]);
240.             AdjustTemplate(true);
241.          }
242. //+------------------------------------------------------------------+
243.       ~C_ChartFloatingRAD()
244.          {
245.             ChartRedraw();
246.             ObjectsDeleteAll(m_Init.id, m_Info.szObj_Chart);
247.             if (!m_Info.IsSaveState)
248.                FileDelete(m_Info.szFileNameTemplate);
249.          }
250. //+------------------------------------------------------------------+
251.       void SaveState(void)
252.          {
253. #define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B);
254.             
255.             uCast_Double info;
256.             
257.             info._16b[0] = m_Info.x;
258.             info._16b[1] = m_Info.y;
259.             info._16b[2] = m_Info.minx;
260.             info._16b[3] = m_Info.miny;
261.             macro_GlobalVariable(macro_NameGlobalVariable("POST"), info.dValue);
262.             m_Info.IsSaveState = true;
263.             
264. #undef macro_GlobalVariable
265.          }
266. //+------------------------------------------------------------------+
267.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
268.          {
269. #define macro_AdjustMinX(A, B)    {                          \
270.             B = (A + m_Info.Regions[MSG_TITLE_IDE].w) > x;   \
271.             mx = x - m_Info.Regions[MSG_TITLE_IDE].w;        \
272.             A = (B ? (mx > 0 ? mx : 0) : A);                 \
273.                                  }
274. #define macro_AdjustMinY(A, B)   {                           \
275.             B = (A + m_Info.Regions[MSG_TITLE_IDE].h) > y;   \
276.             my = y - m_Info.Regions[MSG_TITLE_IDE].h;        \
277.             A = (B ? (my > 0 ? my : 0) : A);                 \
278.                                  }
279.                               
280.             static short sx = -1, sy = -1, sz = -1;
281.             static eObjectsIDE obj = MSG_NULL;
282.             short x, y, mx, my;
283.             double dvalue;
284.             bool b1, b2, b3, b4;
285.             ushort ev = evChartTradeCloseAll;
286. 
287.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
288.             switch (id)
289.             {
290.                case CHARTEVENT_CUSTOM + evEA_At_ChartTrade:
291.                   if (m_Init.bOk = ((lparam >= 0) && (lparam < 2)))
292.                      CurrentSymbol((bool)lparam);
293.                   AdjustTemplate(true);
294.                   break;
295.                case CHARTEVENT_CHART_CHANGE:
296.                   if (!m_Init.bOk)
297.                      EventChartCustom(m_Init.id, evChartTrade_At_EA, 0, 0, "");
298.                   x = (short)ChartGetInteger(m_Init.id, CHART_WIDTH_IN_PIXELS);
299.                   y = (short)ChartGetInteger(m_Init.id, CHART_HEIGHT_IN_PIXELS);
300.                   macro_AdjustMinX(m_Info.x, b1);
301.                   macro_AdjustMinY(m_Info.y, b2);
302.                   macro_AdjustMinX(m_Info.minx, b3);
303.                   macro_AdjustMinY(m_Info.miny, b4);
304.                   if (b1 || b2 || b3 || b4) AdjustTemplate();
305.                   break;
306.                case CHARTEVENT_MOUSE_MOVE:
307.                   if (CheckClick(C_Mouse::eClickLeft))
308.                   {                  
309.                      switch (CheckMousePosition(x, y))
310.                      {
311.                         case MSG_MAX_MIN:
312.                            if (sz < 0) m_Info.IsMaximized = (m_Info.IsMaximized ? false : true);
313.                            break;
314.                         case MSG_DAY_TRADE:
315.                            if ((m_Info.IsMaximized) && (sz < 0)) m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true);
316.                            break;
317.                         case MSG_LEVERAGE_VALUE:
318.                            if ((m_Info.IsMaximized) && (sz < 0)) CreateObjectEditable(obj = MSG_LEVERAGE_VALUE, m_Info.Leverage);
319.                            break;
320.                         case MSG_TAKE_VALUE:
321.                            if ((m_Info.IsMaximized) && (sz < 0)) CreateObjectEditable(obj = MSG_TAKE_VALUE, m_Info.FinanceTake);
322.                            break;
323.                         case MSG_STOP_VALUE:
324.                            if ((m_Info.IsMaximized) && (sz < 0)) CreateObjectEditable(obj = MSG_STOP_VALUE, m_Info.FinanceStop);
325.                            break;
326.                         case MSG_TITLE_IDE:
327.                            if (sx < 0)
328.                            {
329.                               ChartSetInteger(m_Init.id, CHART_MOUSE_SCROLL, false);
330.                               sx = x - (m_Info.IsMaximized ? m_Info.x : m_Info.minx);
331.                               sy = y - (m_Info.IsMaximized ? m_Info.y : m_Info.miny);
332.                            }
333.                            if ((mx = x - sx) > 0) ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_XDISTANCE, mx);
334.                            if ((my = y - sy) > 0) ObjectSetInteger(m_Init.id, m_Info.szObj_Chart, OBJPROP_YDISTANCE, my);
335.                            if (m_Info.IsMaximized)
336.                            {
337.                               m_Info.x = (mx > 0 ? mx : m_Info.x);
338.                               m_Info.y = (my > 0 ? my : m_Info.y);
339.                            }else
340.                            {
341.                               m_Info.minx = (mx > 0 ? mx : m_Info.minx);
342.                               m_Info.miny = (my > 0 ? my : m_Info.miny);
343.                            }
344.                            break;
345.                         case MSG_BUY_MARKET:
346.                            ev = evChartTradeBuy;
347.                         case MSG_SELL_MARKET:
348.                            ev = (ev != evChartTradeBuy ? evChartTradeSell : ev);
349.                         case MSG_CLOSE_POSITION:
350.                            if ((m_Info.IsMaximized) && (sz < 0) && (m_Init.bOk))
351.                            {
352.                               string szTmp = StringFormat("%d?%s?%s?%c?%d?%.2f?%.2f", ev, _Symbol, GetInfoTerminal().szSymbol, (m_Info.IsDayTrade ? 'D' : 'S'),
353.                                                           m_Info.Leverage, FinanceToPoints(m_Info.FinanceTake, m_Info.Leverage), FinanceToPoints(m_Info.FinanceStop, m_Info.Leverage));                           
354.                               PrintFormat("Send %s - Args ( %s )", EnumToString((EnumEvents) ev), szTmp);
355.                               EventChartCustom(m_Init.id, ev, 0, 0, szTmp);
356.                            }
357.                            break;
358.                      }
359.                      if (sz < 0)
360.                      {
361.                         sz = x;
362.                         AdjustTemplate();
363.                         if (obj == MSG_NULL) DeleteObjectEdit();
364.                      }
365.                   }else
366.                   {
367.                      sz = -1;
368.                      if (sx > 0)
369.                      {
370.                         ChartSetInteger(m_Init.id, CHART_MOUSE_SCROLL, true);                  
371.                         sx = sy = -1;
372.                      }
373.                   }
374.                   break;
375.                case CHARTEVENT_OBJECT_ENDEDIT:
376.                   switch (obj)
377.                   {
378.                      case MSG_LEVERAGE_VALUE:
379.                      case MSG_TAKE_VALUE:
380.                      case MSG_STOP_VALUE:
381.                         dvalue = StringToDouble(ObjectGetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_TEXT));
382.                         if (obj == MSG_TAKE_VALUE)
383.                            m_Info.FinanceTake = (dvalue <= 0 ? m_Info.FinanceTake : dvalue);
384.                         else if (obj == MSG_STOP_VALUE)
385.                            m_Info.FinanceStop = (dvalue <= 0 ? m_Info.FinanceStop : dvalue);
386.                         else
387.                            m_Info.Leverage = (dvalue <= 0 ? m_Info.Leverage : (short)MathFloor(dvalue));
388.                         AdjustTemplate();
389.                         obj = MSG_NULL;
390.                         ObjectDelete(m_Init.id, m_Info.szObj_Editable);
391.                         break;
392.                   }
393.                   break;
394.                case CHARTEVENT_OBJECT_DELETE:
395.                   if (sparam == m_Info.szObj_Chart) macro_CloseIndicator(C_Terminal::ERR_Unknown);
396.                   break;
397.             }
398.             ChartRedraw();
399.          }
400. //+------------------------------------------------------------------+
401. };
402. //+------------------------------------------------------------------+
403. #undef macro_NameGlobalVariable
404. #undef macro_CloseIndicator
405. //+------------------------------------------------------------------+

Header file C_ChartFloatingRAD.mqh

Although this code may appear extensive, anyone following this replay/simulation series will notice that most of it remains unchanged. Modifications are minimal for those studying the code. However, you will notice that many calls to GetInfoTerminal have been replaced by m_Init.id. This change was made because accessing a variable is considerably faster than calling a function.

On line 224, you can see where the chart ID is captured for subsequent use throughout the class. The key point, however, is the CHARTEVENT_MOUSE_MOVE event on line 306. Notice that although line 307 calls the C_Mouse class, it will return false if the mouse indicator is absent. When the indicator is present, it will return true.

Pay attention to line 309, where the CheckMousePosition procedure does not use the lparam and dparam data, which would normally contain mouse position values. Upon reviewing this procedure on line 140, you will see that line 145 retrieves the position data. It is interpreted by the C_Terminal class. Lines 145 and 146 specify the values we want to use. In this case, we use absolute values, although relative values could also be used.

The values come from C_Terminal's interpretation rather than the mouse indicator buffer. This approach improves execution efficiency and speed. Nonetheless, the same values are also stored in the mouse indicator buffer.

Finally, we come to the last source file, which also underwent changes due to modifications in the C_ChartFloatingRAD constructor. This is the source code for the Chart Trade indicator. This can be seen just below:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Chart Trade Base Indicator."
04. #property description "See the articles for more details."
05. #property version   "1.82"
06. #property icon "/Images/Market Replay/Icons/Indicators.ico"
07. #property link "https://www.mql5.com/pt/articles/12580"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh>
12. //+------------------------------------------------------------------+
13. #define def_ShortName "Indicator Chart Trade"
14. //+------------------------------------------------------------------+
15. C_ChartFloatingRAD *chart = NULL;
16. //+------------------------------------------------------------------+
17. input ushort user01 = 1;         //Leverage
18. input double user02 = 100.1;     //Finance Take
19. input double user03 = 75.4;      //Finance Stop
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    chart = new C_ChartFloatingRAD(def_ShortName, user01, user02, user03);
24.    
25.    if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED;
26. 
27.    return INIT_SUCCEEDED;
28. }
29. //+------------------------------------------------------------------+
30. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
31. {
32.    return rates_total;
33. }
34. //+------------------------------------------------------------------+
35. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
36. {
37.    if (_LastError < ERR_USER_ERROR_FIRST) 
38.       (*chart).DispatchMessage(id, lparam, dparam, sparam);
39. }
40. //+------------------------------------------------------------------+
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. }
55. //+------------------------------------------------------------------+

Chart Trade indicator source code

Note that the only change occurred on line 23. For those who have not seen previous versions, the complete code is provided for reference.


Final Thoughts

In this article, I presented all changes made to the code to enhance its sustainability and maintainability. In the next article, we will cover a different topic. Attached to this article, you will have access to the compiled applications for the Mouse indicator and Chart Trade indicator, allowing you to use the latest versions in your trading. We will continue in the next article.

File 03 Description
Experts\Expert Advisor.mq5
Demonstrates the interaction between Chart Trade and the Expert Advisor (Mouse Study is required for interaction)
Indicadores\Chart Trade.mq5 Creates the window for configuring the order to be sent (Mouse Study is required for interaction)
Indicadores\Market Replay.mq5 Creates controls for interacting with the replay/simulator service (Mouse Study is required for interaction)
Indicadores\Mouse Study.mq5 Enables interaction between graphical controls and the user (Required for operating both the replay simulator and live market trading)
Servicios\Market Replay.mq5 Creates and maintains the market replay and simulation service (Main file of the entire system)


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

Attached files |
Anexo.zip (490.53 KB)
Reusing Invalidated Orderblocks As Mitigation Blocks (SMC) Reusing Invalidated Orderblocks As Mitigation Blocks (SMC)
In this article, we explore how previously invalidated orderblocks can be reused as mitigation blocks within Smart Money Concepts (SMC). These zones reveal where institutional traders re-enter the market after a failed orderblock, providing high-probability areas for trade continuation in the dominant trend.
How to publish code to CodeBase: A practical guide How to publish code to CodeBase: A practical guide
In this article, we will use real-life examples to illustrate posting various types of terminal programs in the MQL5 source code base.
From Novice to Expert: Market Periods Synchronizer From Novice to Expert: Market Periods Synchronizer
In this discussion, we introduce a Higher-to-Lower Timeframe Synchronizer tool designed to solve the problem of analyzing market patterns that span across higher timeframe periods. The built-in period markers in MetaTrader 5 are often limited, rigid, and not easily customizable for non-standard timeframes. Our solution leverages the MQL5 language to develop an indicator that provides a dynamic and visual way to align higher timeframe structures within lower timeframe charts. This tool can be highly valuable for detailed market analysis. To learn more about its features and implementation, I invite you to join the discussion.
Price Action Analysis Toolkit Development (Part 44): Building a VWMA Crossover Signal EA in MQL5 Price Action Analysis Toolkit Development (Part 44): Building a VWMA Crossover Signal EA in MQL5
This article introduces a VWMA crossover signal tool for MetaTrader 5, designed to help traders identify potential bullish and bearish reversals by combining price action with trading volume. The EA generates clear buy and sell signals directly on the chart, features an informative panel, and allows for full user customization, making it a practical addition to your trading strategy.