Market Simulation (Part 08): Sockets (II)
Introduction
In the previous article, Market Simulation (Part 07): Sockets (I), I demonstrated the first steps to get you started with studying sockets. However, the application shown there might not have been particularly exciting. To be honest, it was more like a "HELLO WORLD" program - the first program we usually try when learning something new in programming. It was a first step on a long journey.
So, in this article, I will show you how to create something a bit more interesting. To be clear, it won't be particularly useful, but it will allow you to experiment and have some fun while studying the concepts involved. Just like in the previous article, we will need to rely on external code. Again, the reason for using external code is that I don't want to add DLLs to MetaTrader 5, at least not yet.
Regarding the external code, I won't go into too much detail about how it works. You can find various codes that perform the same type of task or create your own by studying the references I mentioned in the previous article. So, what are we going to do in this article? Essentially, I want to demonstrate how sockets can be interesting. I also intend to make some use of them in the replay/simulator system. We will approach this in the simplest way possible: by creating a Mini Chat. That's right - I will show you how to create a Mini Chat in MetaTrader 5.
Planning
The concept behind a mini chat is quite simple and obvious. You have an area where you can type messages, a button to send them, and another area to view messages from others. In short, it's easy to implement. We will need to add an edit box, a button, and an object to display posted messages. MetaTrader 5 provides all of these tools.
Good. Typically, chat programs use a built-in client-server system. But here, to use pure MQL5, the server will be an external program, while the clients will be implemented in MQL5. This is one of the advantages of using sockets: you aren't limited to a single approach. You can implement them in multiple ways. You might be thinking: How can we manage connections to allow any number of participants in our mini chat? That must be very complicated to develop. Right? Well, it depends on what you want to achieve and how you implement it.
The server program I will demonstrate is so simple that you could run it on a Raspberry Pi and use it as a mini server. This setup could support a large number of participants without recompiling the code because the server will be dynamic. By "dynamic," I mean that the connection limit will be determined by the operating system or hardware capabilities, not by the server code itself. Before diving into the server code, let's look at the client-side part. It will be implemented in MQL5.
Basic Implementation
This part of the implementation will be quite interesting, especially within MQL5, since we will use a somewhat unusual approach. First, we need to create a window to interact with the mini chat. The simplest way to do this in MQL5 for MetaTrader 5 is by using an indicator. However, there's a caveat: indicators cannot use sockets because, depending on the socket implementation, they can block the calculation flow of other indicators. All indicators share the same calculation space. So how can we create the window? The simplest code is as follows:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property indicator_separate_window 04. #property indicator_plots 0 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. return INIT_SUCCEEDED; 09. } 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. return rates_total; 14. } 15. //+------------------------------------------------------------------+
Initial indicator code
This basic code creates a window for us, thanks to line 3, which indicates that adding this code to a chart will generate a new window. But there's a problem. If we leave it as is, users could add multiple chat windows to the chart. Another issue is that indicators cannot use sockets.
We need to modify the code so it can be used in a context where sockets are allowed. To simplify, we will embed everything into an Expert Advisor. This is just for demonstration. The same could be done with a script. However, scripts are removed when the chart timeframe changes, so I will show the implementation using an Expert Advisor. The first step is creating the following code:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. //+------------------------------------------------------------------+ 05. #define def_IndicatorMiniChat "Indicators\\Mini Chat\\Mini Chat.ex5" 06. #resource "\\" + def_IndicatorMiniChat 07. //+------------------------------------------------------------------+ 08. long gl_id; 09. int subWin; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. gl_id = ChartID(); 14. subWin = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL); 15. 16. ChartIndicatorAdd(gl_id, subWin, iCustom(NULL, 0, "::" + def_IndicatorMiniChat)); 17. 18. return INIT_SUCCEEDED; 19. } 20. //+------------------------------------------------------------------+ 21. void OnDeinit(const int reason) 22. { 23. ChartIndicatorDelete(gl_id, subWin, ChartIndicatorName(gl_id, subWin, 0)); 24. } 25. //+------------------------------------------------------------------+ 26. void OnTick() 27. { 28. } 29. //+------------------------------------------------------------------+ 30. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 31. { 32. } 33. //+------------------------------------------------------------------+
Expert Advisor launch code
This is the basic skeleton of our Expert Advisor, allowing a window to open. Notice line 5: it specifies that in line 6 we will use the indicator as an internal resource of the Expert Advisor. Why? Because we want the indicator to create the window for us. But the Expert Advisor cannot do this directly. This is why we use an indicator.
When the Expert Advisor is attached to a chart, it will add the indicator (line 16) to create the necessary window. However, the previous indicator code isn't sufficient because it can be added to a chart directly by the user. We want the Expert Advisor to add it automatically, preventing manual user addition.
How do we solve this? Simple. we modify the indicator so that users cannot place it on the chart. Once the Expert Advisor is compiled, the indicator executable could even be deleted since it will be embedded. However, we also need a safeguard in case the executable is accessible. The solution is in the following code:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Base indicator for Mini Chat." 04. #property description "It cannot be used without outside assistance." 05. #property version "1.00" 06. #property indicator_chart_window 07. #property indicator_plots 0 08. //+------------------------------------------------------------------+ 09. #define def_ShortName "Mini Chat" 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. long id = ChartID(); 14. string sz0 = def_ShortName + "_TMP"; 15. int i0; 16. 17. IndicatorSetString(INDICATOR_SHORTNAME, sz0); 18. for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--) 19. if (ChartIndicatorName(id, c0, 0) == def_ShortName) 20. { 21. ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0); 22. return INIT_FAILED; 23. } 24. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 25. i0 = ChartWindowFind(id, def_ShortName); 26. if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1)) 27. { 28. ChartIndicatorDelete(id, i0, def_ShortName); 29. return INIT_FAILED; 30. } 31. 32. return INIT_SUCCEEDED; 33. } 34. //+------------------------------------------------------------------+ 35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 36. { 37. return rates_total; 38. } 39. //+------------------------------------------------------------------+
First modification of the indicator code
This code prevents the user from placing the indicator on the chart, ensuring it can only be added by the Expert Advisor. Let's try to understand why this code does not allow the user to place the indicator on the chart. Notice that in line 06 we specify that the indicator will not create a new window.
Line 09 defines a name for the indicator. This name will be used in tests. Now note that in order to remove an indicator from the list, we need it to have a name. This name SHOULD NOT BE FINAL, but it should be temporary. This is done in line 14. We then set this temporary name as the indicator name, which is done in line 17.
Now comes the interesting part: In the loop on line 18, we'll search for the indicator name in the list of indicators on the chart. However, the name we're looking for is on line 09. The test that does this is on line 19. When the indicator is found, line 21 is executed. This line deletes not the indicator that is already on the chart, but the one we are trying to place. Immediately after this, on line 22, we report that initialization failed.
But if the indicator can be placed, 24 sets the final name for the embedded indicator, ensuring only the Expert Advisor can use it.
There’s one more detail. This prevents a user from adding the indicator after it is already on the chart. But a user could still try adding it initially. To prevent this, we perform an additional check.
Line 25 detects which window the indicator is being placed in. If the indicator is located in the main window, the value will be 0. Otherwise the subwindow number is returned. Line 26 checks this value. If it's 0, the indicator is removed. If there's more than one indicator in the window, it's also removed.
This might be confusing. "If the EA is going to place an indicator on a chart, won't it do so in the main window? Isn't this what line 06 indicates, where it says that the indicator will be placed in the main window?" In fact, when the EA places an indicator on the chart, it creates a new window. So if the value returned in line 25 is 0, we can safely remove the indicator. But we can also remove it if there is any other element in the same return window, as this will indicate that it is the user who is trying to place the indicator.
This ensures that the user cannot manually add the indicator to the chart. Once this is achieved, we can move on to the next step.
Implementing Interaction Objects
To simplify our MQL5 application, we will do the following: interaction objects will be placed in the Indicator, while the connection logic will be located in the Expert Advisor. The design will allow for future enhancements to the mini chat. We will organize the work using header files. Since everything we are doing is part of the replay/simulator project, we will integrate the mini chat into the same framework. The first step is to add two new events to our event enumeration. This code can be seen below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket //Event To Mini Chat 47. }; 48. //+------------------------------------------------------------------+
Defines.mqh file source code
Notice that the old file Defines.mqh has received two new lines: lines 45 and 46. These lines will allow us to communicate via sockets. However, these events were added solely because we are separating the mini chat from the internal code of the Expert Advisor. In other words, we need a way for the indicator to display messages received via the socket. And since the indicator is prohibited from using sockets directly, the Expert Advisor will monitor the socket and, as soon as a message is available, forward it to the mini chat. This allows us to access the messages within the indicator.
Good. Now let's examine the header file responsible for creating objects on the chart. This can be seen below:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ShortName "Mini Chat" 007. #define def_MaxRows 256 008. #define def_FontName "Lucida Console" 009. #define def_FontSize 12 010. #define def_SizeControls (m_txtHeight + 6) 011. #define macroColorRGBA(A) ((uint)((0xFF << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16))) 012. //+------------------------------------------------------------------+ 013. class C_Chat 014. { 015. private : 016. long m_id; 017. int m_sub; 018. int m_txtHeight; 019. bool m_full; 020. ushort m_Width, 021. m_Height, 022. m_index; 023. uint m_Pixel[]; 024. string m_ObjEdit, 025. m_ObjBtn, 026. m_ObjPanel; 027. struct st0 028. { 029. string info; 030. bool loc; 031. }m_Msgs[def_MaxRows + 1]; 032. //+------------------------------------------------------------------+ 033. void Add(string szMsg, bool isloc = false) 034. { 035. m_Msgs[m_index].info = szMsg; 036. m_Msgs[m_index].loc = isloc; 037. if ((++m_index) > def_MaxRows) 038. { 039. m_full = true; 040. m_index = 0; 041. } 042. Paint(); 043. }; 044. //+------------------------------------------------------------------+ 045. void Paint(void) 046. { 047. int max, count, p0, p1; 048. 049. ArrayInitialize(m_Pixel, macroColorRGBA(clrBlack)); 050. if ((p0 = m_Height - def_SizeControls) < 0) return; 051. max = (int)(floor(p0 / (m_txtHeight * 1.0))); 052. p1 = m_index - max; 053. if (m_full) 054. count = (max > def_MaxRows ? m_index + 1 : (p1 > 0 ? p1 : (def_MaxRows + p1 + 1))); 055. else 056. count = (p1 > 0 ? p1 : 0); 057. for (ushort row = 0; row < p0; count++) 058. { 059. count = (count > def_MaxRows ? 0 : count); 060. if (count == m_index) break; 061. TextOut(m_Msgs[count].info, 2, row, 0, m_Pixel, m_Width, m_Height, macroColorRGBA(m_Msgs[count].loc ? clrSkyBlue : clrLime), COLOR_FORMAT_ARGB_NORMALIZE); 062. row += (ushort) m_txtHeight; 063. } 064. ResourceCreate("::" + m_ObjPanel, m_Pixel, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE); 065. ChartRedraw(); 066. } 067. //+------------------------------------------------------------------+ 068. void CreateObjEdit(const string szArg) 069. { 070. ObjectCreate(m_id, szArg, OBJ_EDIT, m_sub, 0, 0); 071. ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2); 072. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0); 073. ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls); 074. ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName); 075. ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize); 076. ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrDarkGray); 077. ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack); 078. ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrNavy); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateObjButton(const string szArg, const string szTxt) 082. { 083. ObjectCreate(m_id, szArg, OBJ_BUTTON, m_sub, 0, 0); 084. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0); 085. ObjectSetInteger(m_id, szArg, OBJPROP_XSIZE, 70); 086. ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls); 087. ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName); 088. ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize); 089. ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrSkyBlue); 090. ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack); 091. ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrBlack); 092. ObjectSetString(m_id, szArg, OBJPROP_TEXT, szTxt); 093. } 094. //+------------------------------------------------------------------+ 095. void CreateObjPanel(const string szArg) 096. { 097. ObjectCreate(m_id, szArg, OBJ_BITMAP_LABEL, m_sub, 0, 0); 098. ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2); 099. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, m_txtHeight + 8); 100. ObjectSetString(m_id, szArg, OBJPROP_BMPFILE, "::" + m_ObjPanel); 101. } 102. //+------------------------------------------------------------------+ 103. public : 104. //+------------------------------------------------------------------+ 105. C_Chat() 106. :m_index(0), 107. m_full(false), 108. m_Width(0), 109. m_Height(0) 110. { 111. int tmp; 112. 113. m_sub = ChartWindowFind(m_id = ChartID(), def_ShortName); 114. TextSetFont(def_FontName, -10 * def_FontSize, 0, 0); 115. TextGetSize("M", tmp, m_txtHeight); 116. CreateObjEdit(m_ObjEdit = def_ShortName + " Edit" + (string)ObjectsTotal(m_id)); 117. CreateObjButton(m_ObjBtn = def_ShortName + " Button" + (string)ObjectsTotal(m_id), "Send"); 118. CreateObjPanel(m_ObjPanel = def_ShortName + " Panel" + (string)ObjectsTotal(m_id)); 119. } 120. //+------------------------------------------------------------------+ 121. ~C_Chat() 122. { 123. ObjectsDeleteAll(m_id, def_ShortName); 124. }; 125. //+------------------------------------------------------------------+ 126. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 127. { 128. switch(id) 129. { 130. case CHARTEVENT_CHART_CHANGE: 131. m_Width = (ushort)ChartGetInteger(m_id, CHART_WIDTH_IN_PIXELS, m_sub); 132. m_Height = (ushort)ChartGetInteger(m_id, CHART_HEIGHT_IN_PIXELS, m_sub); 133. ObjectSetInteger(m_id, m_ObjEdit, OBJPROP_XSIZE, m_Width - 75); 134. ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_XDISTANCE, m_Width - 72); 135. ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_XSIZE, m_Width - 4); 136. ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_YSIZE, m_Height - 4); 137. ArrayResize(m_Pixel, m_Width * m_Height); 138. Paint(); 139. break; 140. case CHARTEVENT_OBJECT_CLICK: 141. if (sparam == m_ObjBtn) 142. { 143. string sz0 = ObjectGetString(m_id, m_ObjEdit, OBJPROP_TEXT); 144. if (sz0 != "") 145. { 146. EventChartCustom(m_id, evChatWriteSocket, 0, 0, sz0); 147. Add(sz0, true); 148. ObjectSetString(m_id, m_ObjEdit, OBJPROP_TEXT, ""); 149. ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_STATE, 0); 150. } 151. } 152. break; 153. case CHARTEVENT_CUSTOM + evChatReadSocket: 154. Add(sparam); 155. break; 156. } 157. } 158. //+------------------------------------------------------------------+ 159. }; 160. //+------------------------------------------------------------------+ 161. #undef macroColorRGBA 162. #undef def_MaxRows 163. //+------------------------------------------------------------------+
C_Chat.mqh file code
This code generates the objects with which we will interact. Since I know many enthusiasts are reading these articles, I ask for patience from those who already have strong programming skills. I will provide a detailed explanation of this code so that anyone wishing to use and improve it can do so. At the same time, it is meant to motivate more readers to develop their own solutions.
Let’s go through the explanations. Line 04 includes the header file containing some of our definitions. However, the definitions we will actually use are those added recently. Line 06 defines a name for our indicator. Line 07 sets the maximum number of lines that our mini chat will display. This will be explained in more detail later. Lines 08 and 09 define the font name and size. This makes it easier to adjust dimensions globally: once set here, the entire mini chat will adapt accordingly. Line 10 specifies the height of our controls, preventing display issues. Line 11 is a macro to place text on the chart. This will be explained further below.
With these definitions in place, we can begin the class. Line 15 declares the private section. All variables and functions declared after this point belong exclusively to the class C_Chat. Lines 16 to 26 define the minimum set of variables needed. If you wish to add more features to the mini chat, you should increase the number of objects. This should be done here. Line 27 declares a structure for storing messages, providing fast access to messages posted by other users.
Important detail: the server will not store messages. Any messages posted before you connect will be lost. There's another important detail: changing the chart timeframe will remove all chart objects. So, messages stored in this structure will also be lost. To preserve messages, you need to implement a method to save and restore them to a file. All you need to do is save the structure defined in line 27 and, when reading it back, call the procedure explained below. It is simple.
Now look at line 31. It uses the value defined in line 7. Note that we are adding one because MQL5, like C/C++, uses zero-based indexing. Adding one ensures that the last 256 lines are preserved, as declared. You can adjust this number as needed.
Now we enter the functional part of the class: the procedures that make the mini chat work. The first procedure is in line. This procedure will add new messages to the structure declared in line 27. Lines 35 and 36 assign values to the corresponding fields.
Line 37 checks whether the message counter has reached the maximum. If so, the counter is set to zero. This is done in line 40. To know when this limit is reached, we use line 39, which sets a flag indicating the message list is full.
Why is this necessary? Once the maximum number of messages is reached, older messages will be overwritten. This forms a circular buffer: new messages are written into the oldest positions while maintaining the list at full capacity. This behavior can be modified if desired.
The critical next step is displaying the message on the chart, handled in the procedure beginning at line 45.
Before we analyze that, let's look at some other setup steps to understand line 45 properly. Jump to line 95. Here we create a panel to contain the text from the circular list. We make it very simple. Line 97 creates an OBJ_BITMAP_LABEL object. Lines 98–99 set the top-left corner of the object. The realli important line is line 100m which specifies which bitmap will be used.
But wait. Bitmap? You may wonder why we use a bitmap instead of simple text. That doesn't make sense. However, MQL5 does not provide an easy way to draw text on a chart at precise locations as we need. The Comment() function is insufficient. We need to have control over where and how the text is displayed. Using a bitmap allows us to draw letters precisely, and MetaTrader 5 will render them for display.
Although it sounds complicated, it's actually straightforward. Line 100 simply defines the bitmap name. Now we can go back to line 45. What will be done there will soon begin to make sense.
First, in line 49 we clear the pixel matrix. You can use any color. In this case, the background color is clrBlack, meaning the background of the text panel will be black. Set it to any desired value. Immediately after this, we perform a small calculation to check whether the mini chat window should contain a text panel. If not, we exit. Otherwise we draw a text.
Then, line 51 counts the maximum number of lines within the allowed area of the text pane. Line 52 looks for the starting point of the circular list to show all possible lines. Here's one detail: when the list is full, the variable m_index may be smaller, larger, or equal to the number of lines in the panel. Therefore, we need to correct this value.
Lines 53–56 adjust this index correctly. Now, the variable that will be used for counting is indeed pointing to the oldest line to be displayed. We then enter the loop at line 57. This loop may seem strange to you, but it is quite simple. All it does is iterate through the circular list, position by position, and at line 61, using a call to the MQL5 library, draw the text onto the bitmap.
Once this loop finishes, the bitmap will contain the text drawn on it. We then need to inform MetaTrader 5 that the bitmap can now be used. This is done at line 64. Pay close attention to this sequence of events. First, we define the name of the bitmap. Then we draw it. And finally, we tell MetaTrader 5 that the bitmap can now be displayed on the chart. This is exactly what happens at line 64. Simple, isn't it?
The procedures from line 68 through line 93 are fairly common in MQL5 programming and therefore do not require further explanation. However, we are not finished yet. We now move on to the class constructor, which appears after the declaration of a public clause at line 103. Thus, everything after line 103 will be visible outside the class. In this constructor, located at line 105, we initialize our mini chat. This is done in a few very simple steps.
First, we need to capture the index of the window where the mini chat will be displayed. This is done at line 113. Immediately after, at line 114, we tell MetaTrader 5 how the font to be used for creating bitmap text will be defined. At line 115, we capture the height of the letters. This is because, depending on the operating system configuration, the letters may have different sizes. So, to avoid an odd-looking display, we capture the text height.
Next, between lines 116 and 118, we create the objects we need. If you require additional objects, you should add them at this point. Just be careful to follow the creation rules demonstrated by the objects shown in the code.
The destructor at line 121 has only one task: to remove the objects created from the chart. That is why it contains only line 123.
Finally, we reach line 126, where we handle the messages that MetaTrader 5 will direct to our indicator. Remember, we are still inside the indicator code. To keep things as simple as possible, we handle only three types of events, or messages, here. One of them is CHARTEVENT_CHART_CHANGE, which defines the correct position and dimensions of the objects. This is the first message generated by MetaTrader 5 as soon as our code is placed on the chart.
We also handle the CHARTEVENT_OBJECT_CLICK message, which occurs when a click happens on the chart. In this case, at line 141, we check whether the click was on the send message button. If so, at line 143, we capture the text present in the edit area so that it can be sent shortly afterward. Then, at line 144, we verify that the text is not empty. If it is not, at line 146 we trigger a custom event in order to send this same text to the Expert Advisor.
An important detail: although the Expert Advisor could directly access the text in the edit object, we should not take the risk of letting the Expert Advisor retrieve the text. This is because it may have already been destroyed by the indicator, as a result of line 148. In any case, the same text that we send via the custom event will be displayed in the panel at line 147.
Finally, at line 153, we have a custom event. Its purpose is to capture the text that the Expert Advisor received from the socket and place it in the text panel of our mini chat. At this point, this may seem confusing to you, dear reader, especially if you landed directly on this article. In previous articles in this same series, I explained in detail how to work with custom events. If you are curious, take a look at those earlier articles. They will greatly help your understanding of what is happening here.
Very well, then, as the final code to be presented in this article, let us look at the final version of the Mini Chat Indicator code. You can see it below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Base indicator for Mini Chat." 04. #property description "It cannot be used without outside assistance." 05. #property version "1.00" 06. #property link "https://www.mql5.com/pt/articles/12672" 07. #property indicator_chart_window 08. #property indicator_plots 0 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Mini Chat\C_Chat.mqh> 11. //+------------------------------------------------------------------+ 12. C_Chat *Chat; 13. //+------------------------------------------------------------------+ 14. int OnInit() 15. { 16. long id = ChartID(); 17. string sz0 = def_ShortName + "_TMP"; 18. int i0; 19. 20. IndicatorSetString(INDICATOR_SHORTNAME, sz0); 21. for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--) 22. if (ChartIndicatorName(id, c0, 0) == def_ShortName) 23. { 24. ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0); 25. return INIT_FAILED; 26. } 27. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 28. i0 = ChartWindowFind(id, def_ShortName); 29. if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1)) 30. { 31. ChartIndicatorDelete(id, i0, def_ShortName); 32. return INIT_FAILED; 33. } 34. 35. Chat = new C_Chat(); 36. 37. return INIT_SUCCEEDED; 38. } 39. //+------------------------------------------------------------------+ 40. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 41. { 42. return rates_total; 43. } 44. //+------------------------------------------------------------------+ 45. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 46. { 47. (*Chat).DispatchMessage(id, lparam, dparam, sparam); 48. } 49. //+------------------------------------------------------------------+ 50. void OnDeinit(const int reason) 51. { 52. delete Chat; 53. } 54. //+------------------------------------------------------------------+
Source code of the indicator
Final Thoughts
In this article, we presented the first part of the mini chat code. The Expert Advisor and server code remain to be discussed. As this is a lengthy topic, you can review and experiment with the indicator code so far. In the next article, we will complete the mini chat implementation, including the server, and see it in action.
| File | Description |
|---|---|
| Experts\Expert Advisor.mq5 | Demonstrates the interaction between Chart Trade and the Expert Advisor (Mouse Study is required for interaction) |
| Indicators\Chart Trade.mq5 | Creates a window for configuring the order to be sent (Mouse Study is required for interaction) |
| Indicators\Market Replay.mq5 | Creates controls for interacting with the replay/simulator service (Mouse Study is required for interaction) |
| Indicators\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) |
| VS C++ Server.cpp | Creates and maintains a socket server in C++ (mini chat version) |
| Python code Server.py | Creates and maintains a Python socket for communication between MetaTrader 5 and Excel |
| ScriptsCheckSocket.mq5 | Checks connection with an external socket |
| Indicators\Mini Chat.mq5 | Implements a mini chat as an indicator (requires a server) |
| Experts\Mini Chat.mq5 | Implements a mini chat as an Expert Advisor (requires a server) |
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/12672
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Larry Williams Market Secrets (Part 3): Proving Non-Random Market Behavior with MQL5
Successful Restaurateur Algorithm (SRA)
Building AI-Powered Trading Systems in MQL5 (Part 8): UI Polish with Animations, Timing Metrics, and Response Management Tools
Neural Networks in Trading: Multi-Task Learning Based on the ResNeXt Model (Final Part)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use