
Developing a Replay System (Part 74): New Chart Trade (I)
Introduction
In the previous article, "Developing a Replay System (Part 73): An Unusual Communication (II)", we concluded the second stage of development for our application, which is responsible for generating the replay/simulator. In other words, we've managed to make the entire system behave in a well-structured and functional manner. More specifically, we now have a system capable of presenting something that closely mirrors real market movements.
However, everything we've done so far represents only a portion of the overall task. We are now entering the third phase of development. At this stage, our focus will shift away from the replay/simulator itself. The emphasis now will be on working directly with the live trading server. In other words, we’ll begin developing the necessary tools to open, manage, and close positions.
Some of these tools have been discussed earlier in this same series. However, since the replay/simulation application has undergone many changes over time, we'll need to either recreate or at least adapt older tools to fit the new model. The first tool we'll be working on is the Chart Trade. This tool is designed to assist us in opening and closing positions, executing market orders. MetaTrader 5 already includes such functionality in its standard installation. It has the One Click Trading buttons, as shown in the image below:
Although these buttons are quite useful and work perfectly well, they are of no use to us, because they are designed to communicate directly with the live trading server. Our replay/simulation system, however, is not an actual server. It is a service, or better put, a set of applications, designed to simulate what a real server would be like.
For that reason, we need to create our own tools. Thus we will be able to replicate the built-in functionality of MetaTrader 5, which we cannot use since we are working in a simulated environment.
This brings us to an important point: all the effort I've put here is driven by the decision to build this entire system exclusively in MQL5. This constraint makes the development process especially interesting from a technical standpoint. Creating something that behaves like a real trading server, purely in MQL5, has been a fascinating challenge. From a purely programming perspective, it would be considerably simpler to create an application in C or C++ using sockets. Such an application could implement all necessary protocols so that MetaTrader 5 would perceive it as a real server.
While this alternative approach would simplify many aspects, allowing us to use MetaTrader 5 in its standard configuration - it wouldn't contribute to our understanding of MQL5. If everything were implemented in C/C++, there would be no need to use MQL5 at all. That would mean missing out on a great deal of valuable knowledge about how to program effectively in MQL5. Since I accepted the challenge of building something as complex as a simulator using pure MQL5, I've managed to stay true to that goal thus far. But now, we're entering a phase where we'll take a break from the replay/simulator. For a while, you'll see things running in a demo account, but the implementation will always be designed with the simulator in mind.
The Birth of a New Chart Trade
The last time we discussed Chart Trade was in the article "Developing a Replay System (Part 47): Chart Trade Project (VI)". Although we haven't worked on it since then, the time has come to revisit it. However, given how much has changed since that article, much of the old code is no longer useful. It is now completely outdated and should be removed. Still, many of the core concepts from that time remain relevant and applicable. So we'll resume development but with a crucial difference: the code will now be completely restructured to incorporate the new concepts developed up to this point.
One important thing you should understand right now: indicators CANNOT open, modify, or close positions or orders. That responsibility lies solely with the Expert Advisor. However we will not implement Chart Trade as an Expert Advisor. Instead, Chart Trade will be developed as an indicator that communicates with an Expert Advisor to perform the required actions.
If you've fully understood the previous articles in this series, you'll recall that communication between indicators and the service is done via custom events. Using these custom events is by far the most practical way to transfer data between programs within the replay/simulation environment. So, even though we're temporarily shifting focus away from the replay/simulator, all development will still be aligned with that context. With that in mind, the first step will be to define a few new custom event values. The new Defines.mqh file is shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evHideMouse, //Hide mouse price line 31. evShowMouse, //Show mouse price line 32. evHideBarTime, //Hide bar time 33. evShowBarTime, //Show bar time 34. evHideDailyVar, //Hide daily variation 35. evShowDailyVar, //Show daily variation 36. evHidePriceVar, //Hide instantaneous variation 37. evShowPriceVar, //Show instantaneous variation 38. evCtrlReplayInit, //Initialize replay control 39. evChartTradeBuy, //Market buy event 40. evChartTradeSell, //Market sales event 41. evChartTradeCloseAll //Event to close positions 42. }; 43. //+------------------------------------------------------------------+
Defines.mqh file source code
Note that the only changes introduced are found in lines 39, 40, and 41. These lines define the custom events that will be triggered by the Chart Trade indicator to notify the Expert Advisor that some action needs to be taken. It is the Expert Advisor, in fact, that handles the execution: opening and closing of positions. It's important to clarify that we're not talking about orders per se here. The true purpose of the Chart Trade indicator is to replace MetaTrader 5's default system for opening and closing positions, whether that means increasing position size or performing partial closes. The Chart Trade indicator will perform these same functions, but with the added benefit of allowing us to apply the same logic within the replay/simulator environment later on. In this context, operations will always be executed at market. Further ahead, we'll begin work on a separate system that will allow us to use pending orders. But let's take things one step at a time. The simplest concept to implement is market execution.
With that in place, we now need to get the old indicator up and running again. You didn't seriously think we were going to rewrite the entire indicator from scratch, did you? That was never the intention. Doing so would be completely irrational.
So let's take a look at the updated indicator code, which has been modified for several reasons you'll understand as we go along. We'll begin by examining the header file C_AdjustTemplate.mqh. The full code is shown below:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_PATH_BTN "Images\\Market Replay\\Chart Trade" 007. #define def_BTN_BUY def_PATH_BTN + "\\BUY.bmp" 008. #define def_BTN_SELL def_PATH_BTN + "\\SELL.bmp" 009. #define def_BTN_DT def_PATH_BTN + "\\DT.bmp" 010. #define def_BTN_SW def_PATH_BTN + "\\SW.bmp" 011. #define def_BTN_MAX def_PATH_BTN + "\\MAX.bmp" 012. #define def_BTN_MIN def_PATH_BTN + "\\MIN.bmp" 013. #define def_IDE_RAD def_PATH_BTN + "\\IDE_RAD.tpl" 014. #define def_IDE_RAD "Files\\Chart Trade\\IDE_RAD.tpl" 015. //+------------------------------------------------------------------+ 016. #resource "\\" + def_BTN_BUY 017. #resource "\\" + def_BTN_SELL 018. #resource "\\" + def_BTN_DT 019. #resource "\\" + def_BTN_SW 020. #resource "\\" + def_BTN_MAX 021. #resource "\\" + def_BTN_MIN 022. #resource "\\" + def_IDE_RAD as string IdeRad; 023. //+------------------------------------------------------------------+ 024. class C_AdjustTemplate 025. { 026. private : 027. string m_szName[], 028. m_szFind[], 029. m_szReplace[], 030. m_szFileName; 031. int m_maxIndex, 032. m_FileIn, 033. m_FileOut; 034. bool m_bFirst; 035. //+------------------------------------------------------------------+ 036. public : 037. //+------------------------------------------------------------------+ 038. C_AdjustTemplate(const string szFile, const bool bFirst = false) 039. :m_maxIndex(0), 040. m_szFileName(szFile), 041. m_bFirst(bFirst), 042. m_FileIn(INVALID_HANDLE), 043. m_FileOut(INVALID_HANDLE) 044. { 045. ResetLastError(); 046. if (m_bFirst) 047. { 048. int handle = FileOpen(m_szFileName, FILE_TXT | FILE_WRITE); 049. FileWriteString(handle, IdeRad); 050. FileClose(handle); 051. } 052. if ((m_FileIn = FileOpen(m_szFileName, FILE_TXT | FILE_READ)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 053. if ((m_FileOut = FileOpen(m_szFileName + "_T", FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 054. } 055. //+------------------------------------------------------------------+ 056. ~C_AdjustTemplate() 057. { 058. FileClose(m_FileIn); 059. FileClose(m_FileOut); 060. FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE); 061. ArrayResize(m_szName, 0); 062. ArrayResize(m_szFind, 0); 063. ArrayResize(m_szReplace, 0); 064. } 065. //+------------------------------------------------------------------+ 066. void Add(const string szName, const string szFind, const string szReplace) 067. { 068. m_maxIndex++; 069. ArrayResize(m_szName, m_maxIndex); 070. ArrayResize(m_szFind, m_maxIndex); 071. ArrayResize(m_szReplace, m_maxIndex); 072. m_szName[m_maxIndex - 1] = szName; 073. m_szFind[m_maxIndex - 1] = szFind; 074. m_szReplace[m_maxIndex - 1] = szReplace; 075. } 076. //+------------------------------------------------------------------+ 077. string Get(const string szName, const string szFind) 078. { 079. for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0]; 080. 081. return NULL; 082. } 083. //+------------------------------------------------------------------+ 084. void Execute(void) 085. bool Execute(void) 086. { 087. string sz0, tmp, res[]; 088. int count0 = 0, i0; 089. 090. if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS)) 091. if ((m_FileIn == INVALID_HANDLE) || (m_FileOut == INVALID_HANDLE)) return false; 092. while (!FileIsEnding(m_FileIn)) 093. { 094. sz0 = FileReadString(m_FileIn); 095. if (sz0 == "<object>") count0 = 1; 096. if (sz0 == "</object>") count0 = 0; 097. if (count0 > 0) if (StringSplit(sz0, '=', res) > 1) 098. { 099. if ((m_bFirst) && ((res[0] == "bmpfile_on") || (res[0] == "bmpfile_off"))) 100. sz0 = res[0] + "=\\Indicators\\Replay\\Chart Trade.ex5::" + def_PATH_BTN + res[1]; 101. sz0 = res[0] + "=\\Indicators\\Chart Trade.ex5::" + def_PATH_BTN + res[1]; 102. i0 = (count0 == 1 ? 0 : i0); 103. for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0); 104. for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0])) 105. { 106. if (StringLen(m_szReplace[c0])) sz0 = m_szFind[c0] + "=" + m_szReplace[c0]; 107. else m_szReplace[c0] = res[1]; 108. } 109. } 110. if (FileWriteString(m_FileOut, sz0 + "\r\n") < 2) return false; 111. }; 112. 113. return true; 114. } 115. //+------------------------------------------------------------------+ 116. }; 117. //+------------------------------------------------------------------+ 118. #undef def_BTN_BUY 119. #undef def_BTN_SELL 120. #undef def_BTN_DT 121. #undef def_BTN_SW 122. #undef def_BTN_MAX 123. #undef def_BTN_MIN 124. #undef def_IDE_RAD 125. #undef def_PATH_BTN 126. //+------------------------------------------------------------------+
C_AdjustTemplate.mqh file source code
Some lines in this code have been struck through. Let's now understand why that happened. Line 14 was modified and replaced by the code now found on line 13. This change reflects a relocation of the template file to a new path. Don't worry as these files are included in the attachment, in case you don't already have them. All you need to do is maintain the same folder structure provided in the attachment. That way, the resources declared here can be located without issue. In addition to that change, a few other lines have also been removed.
Line 45 was removed for reasons already discussed in previous articles. That is, sometimes the _LastError variable contains a value not because an error occurred during execution, but due to some other internal reason. As such, I won't concern myself with the value of _LastError unless the case is special and it is truly relevant.
Now let's look at line 90. Why was this line removed? Because _LastError may hold a value other than ERR_SUCCESS, even though it wasn't caused by our application. To avoid potential misinterpretation or conflicts, we had to make some adjustments here.
What was once a procedure has now been transformed into a function. So let's revisit line 84. You'll see it's been struck through, and replaced by line 85. Why was this change made? The reason is simple: we might encounter occasional errors, and it's better not to rely heavily or directly on the _LastError variable.
You need to understand that this indicator operates in a very strict environment. Unlike pending orders where minor errors can sometimes (later we'll see why "sometimes") be tolerated, this indicator does not allow this. This is because it deals with market execution.
Moreover, there is an even more critical complication which we will cover in a future article. For now, we must ensure that the data in the template file is absolutely accurate.
Therefore, if lines 91 or 110 indicate a failure, we must report this back to the caller located elsewhere. However, if everything is correct, the template file will be updated, and success will be returned on line 113. That simple.
Before wrapping up this explanation about the header file, I'd like to draw your attention to the content of line 101. Line 100 was removed and replaced by this one. Now we have line 101. This is a string. It specifies the expected location of the Chart Trade indicator. In other words, the location has changed.
You now need to ensure the indicator is placed exactly where that string specifies, and that its name matches the one indicated. This is because when this string is used, we are, in fact, accessing bitmap files declared as resources in the indicator. If you change anything, whether it's the code, the name of the indicator, or its folder location, MetaTrader 5 will not be able to access the bitmap files. As a result, the Chart Trade indicator will fail to render properly on the chart.
Although I usually go through all the header files before showing the main source code, this time I'll make an exception. We will skip over a header file which, in fact, is the core of the indicator, and jump straight into the indicator's source code itself. The full code is shown 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.74" 06. #property icon "/Images/Market Replay/Icons/Indicators.ico" 07. #property link "https://www.mql5.com/pt/articles/12413" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. #property indicator_buffers 1 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh> 13. //+------------------------------------------------------------------+ 14. #define def_ShortName "Indicator Chart Trade" 15. //+------------------------------------------------------------------+ 16. C_ChartFloatingRAD *chart = NULL; 17. //+------------------------------------------------------------------+ 18. input long user00 = 0; //ID 19. input ushort user01 = 1; //Leverage 20. input double user02 = 100.1; //Finance Take 21. input double user03 = 75.4; //Finance Stop 22. //+------------------------------------------------------------------+ 23. double m_Buff[]; 24. //+------------------------------------------------------------------+ 25. int OnInit() 26. { 27. bool bErr; 28. 29. chart = new C_ChartFloatingRAD(user00, "Indicator Chart Trade", new C_Mouse(user00, "Indicator Mouse Study"), user01, user02, user03); 30. chart = new C_ChartFloatingRAD(def_ShortName, new C_Mouse(0, "Indicator Mouse Study"), user01, user02, user03); 31. 32. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 33. 34. if (bErr = (_LastError != ERR_SUCCESS)) Print(__FILE__, " - [Error]: ", _LastError); 35. 36. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 37. ArrayInitialize(m_Buff, EMPTY_VALUE); 38. 39. return (bErr ? INIT_FAILED : INIT_SUCCEEDED); 40. return INIT_SUCCEEDED; 41. } 42. //+------------------------------------------------------------------+ 43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 44. { 45. (*chart).MountBuffer(m_Buff, rates_total); 46. 47. return rates_total; 48. } 49. //+------------------------------------------------------------------+ 50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 51. { 52. if (_LastError < ERR_USER_ERROR_FIRST) 53. (*chart).DispatchMessage(id, lparam, dparam, sparam); 54. (*chart).MountBuffer(m_Buff); 55. 56. ChartRedraw(); 57. } 58. //+------------------------------------------------------------------+ 59. void OnDeinit(const int reason) 60. { 61. switch (reason) 62. { 63. case REASON_INITFAILED: 64. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 65. break; 66. case REASON_CHARTCHANGE: 67. (*chart).SaveState(); 68. break; 69. } 70. 71. delete chart; 72. } 73. //+------------------------------------------------------------------+
Chart Trade indicator source code
At a quick glance, this code might seem complicated due to the numerous struck-through lines. The reason there are so many removed lines is because this is a modified version of the original code.
If you haven't seen it yet, the original version is available in the article "Developing a Replay System (Part 47): Chart Trade Project (VI)". But don't be tempted to use the code from that article. This version works very differently. Although this may seem complicated, it's actually quite simple. Because all the complexity has been moved into the header file. So it's extremely important that you understand exactly how this code works in order to fully understand what's happening in the header file we'll examine later.
Let's take it step by step. For the most part, what you're seeing here are removals, more than actual modifications. The first thing we removed is line 10, as you can see it has been struck out.
Now things might seem to get tricky. By removing the indicator property that specifies the number of buffers used, we're telling the compiler that this indicator uses zero buffers. This is because that's the default value for this property. In other words, this indicator won't output any internal buffer data that could be accessed through CopyBuffer.
This might sound like a serious complication. And if you still don't understand what the absence of buffers implies for an indicator, it likely means you haven't yet fully understood how things work in MetaTrader 5 and the MQL5 environment. I'll try to break it down for you, especially if you're still learning and aspiring to become proficient with MQL5.
An indicator serves to assist us (by performing calculations, signaling something, or, as in this case, providing a user interface for interaction). In such use cases, particularly the last one, you could technically use other types of programs, such as scripts. However, the problem with scripts is that when you switch timeframes, they're removed and don't reload automatically. You would have to manually reapply them to the chart. This may be inconvenient. That's why we use indicators instead in such scenarios.
But indicators come with their own issues, especially when you want to use timers. If you add a timer to an indicator, it affects all other indicators on the same chart. So when we need timed execution without relying on scripts, we usually turn to Expert Advisors (EAs) or services. The choice between them depends entirely on the purpose of the timing.
Sure, Chart Trade could have been implemented as a service or an EA. It doesn't have to be an indicator. Using a service could even be useful if we wanted Chart Trade to work across all charts. The service could then ensure that the Chart Trade objects are correctly displayed in each relevant chart.
The issue with this approach lies in event handling. Services don't have built-in event handlers. They are essentially scripts that are not associated with any chart. So using a service for Chart Trade would mean need to create an indicator. The indicator would just handle events, such as button clicks or other user interactions.
This would make the setup more complicated than it needs to be. What about using Chart Trade in an EA? That sounds more reasonable. To a certain extent, it is. But it's also impractical.
Why? One word: diversification. If you include Chart Trade inside an EA, it will only exist in that single EA. If you later decide to build another EA, for whatever reason, you'd have to re-include the entire Chart Trade code again. Sure, you could modularize it using the #include directive, like we see on line 12, and that would work fine. But what if you improve the Chart Trade code later on? You’d have to recompile every EA that uses it. That's inefficient and error-prone.
Well, we could consider including Chart Trade as part of EAs without having to recompile them all when upgrading. That's something to think about too. We could use something like a DLL. But that would introduce even more complexity than it solves. So ultimately, I decided to keep Chart Trade as an indicator. But unlike before, this version doesn't use any buffers to store data for external access. Things are handled differently now, and we'll cover that in due course. Let's get back to the code as there are still aspects to understand.
Take a look at line 14. This definition appears after the #include on line 12, and that's very important. Unlike global variables, whether a #define appears before or after an #include can control how things behave or even whether certain options are available inside the included file. So be aware of that.
Since the definition in line 14 comes after the inclusion of C_ChartFloatingRAD.mqh, it won't be visible or usable inside that file. If we had placed it before the #include, then it would be visible to the contents of the C_ChartFloatingRAD.mqh header file. So yes, in programming, order matters. Let's continue, as there are still a lot of interesting things ahead.
Line 18 was removed because it's no longer necessary. The user is now responsible for adding the Chart Trade indicator to the chart, so we don't need to define the chart ID. Line 23 was also removed but not because of line 10 being removed. These two lines are unrelated. Line 10 is used to tell us whether the indicator exists. If the answer is yes, then it indicates how many buffers were accessed from outside the indicator via CopyBuffer. But even if we don't allow access to buffers externally, we could still have internal ones. However, since we no longer need any, line 23 was struck out. The same reasoning applies to lines 36 and 37.
You'll notice a lot of code has been struck through in OnInit. Most of this was removed due to changes that will be explained in the next article, where we'll explore the header file C_ChartFloatingRAD.mqh. Out of all the removed code, only three lines are functionally important. Let's go through them, starting with line 30. It initializes the C_ChartFloatingRAD class. This is done using the 'new' operator. But as you probably know, constructors don't return any values. So how do we check if initialization failed? We need another mechanism.
In more modular programs, the constructor should only initialize variables and internal state. But since there's no strict rule against it, we often make the constructor do a bit more. This is where things can get tricky. Without a mechanism to report that a constructor has failed, we cannot solve initialization problems.
Luckily, MQL5 allows us to do this. While this method may not be the most suitable, it works. In line 32 we check the value of _LastError. If it holds a predefined error code, we consider the initialization failed. I'm not sure if I can leave this structure as is, but thanks to line 52 I can. However, we will discuss this later. If line 32 detects that initialization fails, we return an error constant from OnInit. This causes MetaTrader 5 to call OnDeInit, which removes the indicator from the chart. Otherwise, line 40 returns a success constant.
Now look at lines 45 and 54. Both were previously used to set up buffers. However, since we no longer use buffers, these lines are now obsolete and have been removed. Now pay attention to the check on line 52. This conditional may not make much sense right now, but without it, line 53 will definitely fail. Why this happens will become clear in the next article when we look at the C_ChartFloatingRAD class.
Finally, let's look at the OnDeInit() procedure. It is automatically triggered by MetaTrader 5 when the indicator is removed from the chart. Now here's something unusual for OnDeInit. I mean line 64. Why is this line even here? At first glance, it doesn't make sense. That's because you're looking at the code without knowing about the C_ChartFloatingRAD class. If you only consider this file, the only reason for OnDeInit(REASON_INITFAILED) is line 32. But there are other reasons in the C_ChartFloatingRAD class. This is why I centralized this call here. So, line 64 appears. This line ensures the Chart Trade indicator is removed from the chart. And the deeper reasoning will be provided in the next article.
Final Thoughts
In this article, I presented some of the changes made to the Chart Trade indicator. Don't try to fully understand or use this code without first reading the next article, which explains the C_ChartFloatingRAD class. Without that, you'll likely make serious mistakes. Please be a little patient and wait for the next article because what it will tell you is really worth it. You will see a class class where the Chart Trade indicator is exactly implemented.
If you want to compile this code, make sure you've got the data used by the C_AdjustTemplate.mqh header file. These are included in the attachment. That said, you can also create your own images and template model for Chart Trade. Just ensure they're compatible with the expectations set by the C_AdjustTemplate class. That's it for now. Don't forget to check out the next part of this series.
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/12413





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use