Market Simulation (Part 09): Sockets (III)
Introduction
In the previous article Market Simulation (Part 08): Sockets (II), we began developing a practical application that makes use of sockets. The goal was to demonstrate the use of this tool in programming oriented toward MetaTrader 5. It is true that MQL5 does not allow us to create a server directly using pure MQL5. However, since the use of sockets is independent of any particular language or even the operating system, we can still use them in MetaTrader 5 by implementing the programming in MQL5.
However, due to internal reasons of the MetaTrader 5 platform itself, we cannot use indicators together with sockets. Or, more precisely: we cannot place calls to socket-related procedures inside an indicator code. The reason for this is that, if this were possible, we could freeze or compromise the performance of the calculations performed inside indicators.
Nevertheless, nothing prevents us from still using indicators for other purposes. And that is exactly what we did in the previous article, where we created our entire mini chat, including the controls and the text panel, inside an indicator. The details created and placed in the indicator do not interfere in any way with the execution flow of indicators. But without using an indicator, it would be quite complicated to create what was done in the previous article, because we would end up interfering with some area of the chart of the asset being plotted.
In this way, however, we do not have such problems, thus leaving our mini chat isolated in its own window.
That said, the executable that was generated in the previous article is not capable of meeting our needs for the mini chat to actually function. We still need to implement a few more details. So here we will finish creating the necessary support to have the mini chat working in MetaTrader 5. Even so, as mentioned at the beginning, MQL5 is currently not capable of meeting everything we need. Therefore, we will need to resort to external programming. At this point, however, there is something that can be done. I will explain this later in this same article. For now, let's finalize the part that will be done in MQL5.
Implementing the Connection Class
Before we look at the final source code of the Expert Advisor, which will actually allow the mini chat to function, let us examine the header file that implements the connection class. The full code is shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class C_Connection 05. { 06. private : 07. int m_Socket; 08. char m_buff[]; 09. string m_info; 10. bool m_NewLine; 11. public : 12. //+------------------------------------------------------------------+ 13. C_Connection(string addr, ushort port, ushort timeout = 1000) 14. :m_Socket(INVALID_HANDLE), 15. m_info(""), 16. m_NewLine(true) 17. { 18. if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError()); 19. else if (SocketConnect(m_Socket, addr, port, timeout)) return; 20. else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError()); 21. SetUserError(1); 22. } 23. //+------------------------------------------------------------------+ 24. ~C_Connection() 25. { 26. SocketClose(m_Socket); 27. } 28. //+------------------------------------------------------------------+ 29. bool ConnectionWrite(string szMsg) 30. { 31. int len = StringToCharArray(szMsg, m_buff) - 1; 32. 33. if (m_Socket != INVALID_HANDLE) 34. if (len >= 0) 35. if (SocketSend(m_Socket, m_buff, len) == len) 36. return true; 37. Print("Connection Write: FAILED..."); 38. 39. return false; 40. } 41. //+------------------------------------------------------------------+ 42. const string ConnectionRead(ushort timeout = 500) 43. { 44. int ret; 45. 46. if (m_NewLine) 47. { 48. m_info = ""; 49. m_NewLine = false; 50. } 51. ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout); 52. if (ret > 0) 53. { 54. m_info += CharArrayToString(m_buff, 0, ret); 55. for (ret--; (ret >= 0) && (!m_NewLine); ret--) 56. m_NewLine = (m_buff[ret] == '\n') || (m_buff[ret] == '\r'); 57. } 58. 59. return (m_NewLine ? m_info : ""); 60. } 61. //+------------------------------------------------------------------+ 62. }; 63. //+------------------------------------------------------------------+
Header file C_Study.mqh
I want you to pay attention to certain details in this code, because understanding them will allow you to truly understand how to make the mini chat work. Our class contains some global variables that are private, as you can see between lines 07 and 10. Note that all these variables appear after the private clause.
Therefore, they cannot be accessed outside the class. The first thing we actually do when we use this class is to call its constructor. Thus, the first line to be executed is line 13. Now observe that on lines 14, 15, and 16 we are doing a few things. The class global variables are initialized there. A detail: I am doing this here and in this manner out of habit, but mainly to avoid forgetting to do it inside the body of the constructor. Believe it or not, this type of oversight is very common. By doing it this way, I make sure I do not forget to properly initialize the main variables.
Very well. As soon as the constructor begins its execution, on line 18 we use a call to the standard library to try to create a socket. If this fails, we will print a message in MetaTrader 5. If it succeeds, on line 19 we will attempt to connect to the socket specified by the parameters. But unlike what happened on the previous line, here, if we succeed, we will return immediately. The reason for this is that the connection has been established. In the event of failure, line 20 will inform us of what happened and the reason for the failure.
In any case, if it is not possible either to create a socket or to connect to it, line 21 will effectively allow us to inform the main code, which in this case will be the Expert Advisor, that the connection process has failed. When we look at the Expert Advisor code, I will explain how you can detect this. Line 21 will only be executed in the event of a failure during the connection attempt process.
The destructor, which is located on line 24, has a single purpose: to close the socket. For this reason, it has only one line in its body. This is line 26, where the socket we were using is released, provided it was created.
Now let us look at the two main functions of our class. The first is responsible for sending data over the connection. That's right, we are talking about the write function, which is located on line 29. This function needs to be implemented, just like the next one, with some care, because the rest of the code will not change at all, regardless of what you want to send: whether images, sounds, video, or text, as will be done here. What really changes are only these two functions: writing and reading. In this case, we are doing things in the simplest possible way. So, we will not include encryption or any other type of security when sending information. The reason is simple: we are just testing and learning how sockets work.
Given the fact that we will actually transmit only text, the first thing to do is determine how many characters will be transmitted. This is done on line 31. The fact that we subtract one from the number of characters is due to the fact that the received string follows the C/C++ standard, that is, it is terminated by a null character. This character will not be transmitted by us. Since the constructor may have failed during creation or even during connection, we need to check whether the socket is valid before attempting to transmit anything through it. This test is performed on line 33. If the socket is invalid for any reason, the transmission will not occur.
Once this has been tested, we must test one more thing. We need to check the number of characters to be transferred. This is because it makes no sense to try to send something if there is nothing to send. This test is performed on line 34. If all these tests pass successfully, on line 35 we will attempt to send the desired data through the open socket. In this case, we are sending via a normal connection, that is, we are not using any encryption or authentication process. However, MQL5 does allow secure transmissions. Study the documentation to better understand this topic.
If we succeed in sending the data, on line 36 we return a true value to the caller. If any of the tests fail, on line 37 we will print a message in MetaTrader 5, and on line 39 we return a false value to the caller.
Now let us look at the read function, which is located on line 42. Here it is worth taking a brief pause to explain something. Each write function should have a corresponding read function. Not that this is mandatory, but consider the following scenario: you may create several functions to send different kinds of data. In that case, it is appropriate for the data to be received by corresponding functions.
In other words, why would you try to read data representing a video in a function designed to read text? That makes no sense, does it? For this reason, always try to properly match things, both in terms of the type of data being transmitted and the type of protocol being used.
This part of the protocol has nothing to do with a TCP or UDP connection itself, but rather with the way the information was structured before being transmitted. Always think of the data in the socket as resembling a file that you are reading. To correctly understand its contents, you must use the correct reading protocol.
There is no point in trying to read a bitmap using a protocol designed to read a JPEG image. Even though both are images, the way they are structured inside the file is completely different. The same applies here when it comes to sockets. With that warning given, let us move on to the socket read routine.
Unlike what was seen in the article Market Simulation (Part 07): Sockets (I), where we produced an echo on the server, here we do not necessarily remain blocked waiting for the server to respond. In fact, we wait a short amount of time for it to respond. The point is that, regardless of whether it responds faster or slower, we will return to the caller, even before the entire message has been read from the socket.
But how? Can we return to our Expert Advisor code even before fully reading the socket? Yes, we can, but we must take some precautions when doing so. That is why I mentioned earlier that you should pay attention to properly handling the type of information expected on the socket. Here, in our read function, by default we will wait 500 milliseconds before returning. If this time has elapsed, we will still return to the main code, even if no information is present in the socket.
However, if during this time some information does appear, we will read it on line 51. Now pay close attention to the following fact, which is important if you intend to implement your own server. On line 54, we add whatever content is present in the socket to our return string. However this return string will only return something to the caller if, and only if, a newline or carriage return character is found in the character string. To verify this, we use the loop on line 55, which scans the received content in search of the aforementioned characters.
When one of these characters is found, the variable m_NewLine will receive a true value. Thus, on line 59, the content read from the socket is allowed to be passed back to the caller. Likewise, when a new call occurs, the test on line 46 will allow the string content to be cleared, so that a new cycle begins.
Now pay attention: in the socket write function code, which is located on line 29, we do not add the characters expected by the read function. The OBJ_EDIT object does not add such characters either. Likewise, the code that calls the ConnectionWrite function does not do so. So who is adding these characters? The server does. Therefore, if you are going to implement your own server, you must add these characters. Otherwise, the mini chat will not be able to distinguish between one posted message and another.
Doing things this way may seem a bit foolish or pointless. But by doing so, it allows us to achieve something that you will be able to see in the videos included in this article. Very well. Now that we have seen the code for the connection class, let us look at the Expert Advisor code.
Implementing the Advisor Advisor
The complete Expert Advisor code used to implement the mini chat can be seen in full below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Mini Chat for demonstration of using sockets in MQL5." 04. #property link "https://www.mql5.com/pt/articles/12673" 05. #property version "1.00" 06. //+------------------------------------------------------------------+ 07. #define def_IndicatorMiniChat "Indicators\\Mini Chat\\Mini Chat.ex5" 08. #resource "\\" + def_IndicatorMiniChat 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Mini Chat\C_Connection.mqh> 11. #include <Market Replay\Defines.mqh> 12. //+------------------------------------------------------------------+ 13. input string user00 = "127.0.0.1"; //Address 14. input ushort user01 = 27015; //Port 15. //+------------------------------------------------------------------+ 16. long gl_id; 17. int gl_sub; 18. C_Connection *Conn; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. Conn = new C_Connection(user00, user01); 23. if (_LastError > ERR_USER_ERROR_FIRST) 24. return INIT_FAILED; 25. ChartIndicatorAdd(gl_id = ChartID(), gl_sub = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_IndicatorMiniChat)); 26. 27. EventSetTimer(1); 28. 29. return INIT_SUCCEEDED; 30. } 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. EventKillTimer(); 35. delete Conn; 36. ChartIndicatorDelete(gl_id, gl_sub, ChartIndicatorName(gl_id, gl_sub, 0)); 37. } 38. //+------------------------------------------------------------------+ 39. void OnTick() 40. { 41. } 42. //+------------------------------------------------------------------+ 43. void OnTimer() 44. { 45. string sz0 = (*Conn).ConnectionRead(); 46. if (sz0 != "") 47. EventChartCustom(gl_id, evChatReadSocket, 0, 0, sz0); 48. } 49. //+------------------------------------------------------------------+ 50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 51. { 52. if (id == CHARTEVENT_CUSTOM + evChatWriteSocket) 53. (*Conn).ConnectionWrite(sparam); 54. } 55. //+------------------------------------------------------------------+
Expert Advisor source code
Note that the code is quite simple. We practically do not need to program much here. Even so, there are a few precautions to take. Observe that on lines 07 and 08 we are adding the indicator created in the previous article to the Expert Advisor code. This way, once the Expert Advisor code is compiled, the indicator executable can be deleted, because it will be embedded inside the Expert Advisor. In the previous article I explained the reasons for this measure. The code proceeds without many changes compared to what was seen in the earlier article.
The detail now is that a call was included in order to receive timer events. This is done on line 27. Thus, approximately once per second, the code responsible for handling timer events will be executed, that is, we will have a call to OnTime. What really matters in this Expert Advisor code is precisely this OnTime call and the OnChartEvent call. These two are what allow the mini chat to function in the way it is being implemented. Let's first understand the OnTime procedure.
Whenever MetaTrader 5 triggers an OnTime event, our Expert Advisor will intercept this call. Then, on line 45, we attempt to read from the socket specified on lines 13 and 14. That's right – we will be connecting to a network. This was explained two articles ago, where I discussed sockets. If you have any doubts about this, read the article Sockets (I).
Very well. The read attempt may return a string or not. If we receive a return from the read function, the test on line 46 will succeed. In that case, we trigger a custom event, shown on line 47. The purpose of this event is to allow the mini chat indicator to access the information posted to the socket. If the mini chat were implemented directly inside the Expert Advisor, such a custom event would be completely unnecessary. But since we separated things, we must send the data to the indicator so it can handle displaying the information in the text panel. See how interesting this becomes: depending on what we want to display, we must adapt to the best way to implement the solution.
There are no further details regarding the OnTime procedure. It is that simple. Likewise, the OnChartEvent procedure is also very simple. This time, we intercept a request from the mini chat indicator to write to the socket. To check whether we have such a call, we use the test on line 52. If it is confirmed that we are handling a custom event, line 53 forwards the data provided by the mini chat indicator to the write function.
In this communication system there is one detail that may not be so obvious to those unfamiliar with event-driven programming. The detail is that if any other program (another indicator, a script, or even a service) triggers a custom event with the same value as evChatReadSocket, the mini chat indicator will interpret it as if it came from the Expert Advisor. The same occurs if a custom event is triggered with the same value as evChatWriteSocket. The Expert Advisor will interpret it as if it came from the mini chat indicator.
I do not know whether you noticed the possibilities this opens. Especially if you know the address and port that will receive the data you send through the socket, and likewise can read from a given address or port. This type of mechanism opens the possibility of creating a small “spy” that observes what is happening in a specific place. That is why we must always properly encrypt and encode everything. But since here we are using this only for demonstration purposes, the risk of data leakage is minimized, practically zero. As you will see in the videos, you can test a network connection even without having an actual network installed.
Since everything up to this point is part of MQL5, you will probably be satisfied with the results and able to implement the server yourself. But for those who do not know how to implement a server, let us look at that in a new section, purely out of curiosity.
Implementing the Server for the Mini Chat
Implementing a server is far from being a complex task. In fact, it is quite simple. However, implementing a server is different from putting it into operation. Implementation refers to creating the code. Putting it into operation refers to ensuring it does not leak anything it was not supposed to expose in the first place.
Here we will not cover the operational aspects of running a server. We will deal purely with the implementation part. But I want to make it clear that the code you will see should not be used for production purposes. The code shown is intended to be didactic. In other words, do not use it without fully understanding what you are doing. Use it solely as a way to learn and understand how a server is actually implemented. The complete code is shown below:
001. #define WIN32_LEAN_AND_MEAN 002. 003. #include <winsock2.h> 004. #include <iostream> 005. #include <sstream> 006. #include <stdlib.h> 007. 008. #pragma comment (lib, "Ws2_32.lib") 009. 010. #define DEFAULT_BUFLEN 256 011. #define DEFAULT_PORT 27015 012. 013. using namespace std; 014. 015. int __cdecl main(void) 016. { 017. WSADATA wsData; 018. SOCKET listening, slave; 019. sockaddr_in hint; 020. fd_set master; 021. bool Looping = true; 022. int ConnectCount; 023. string szMsg; 024. 025. if (WSAStartup(MAKEWORD(2, 2), &wsData) != EXIT_SUCCESS) 026. { 027. cout << "Can't Initialize WinSock! Quitting..." << endl; 028. return EXIT_FAILURE; 029. } 030. if ((listening = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) 031. { 032. cerr << "Can't create a socket! Quitting" << endl; 033. WSACleanup(); 034. return EXIT_FAILURE; 035. } 036. else 037. cout << "Creating a Socket..." << endl; 038. 039. hint.sin_family = AF_INET; 040. hint.sin_port = htons(DEFAULT_PORT); 041. hint.sin_addr.S_un.S_addr = INADDR_ANY; 042. 043. if (bind(listening, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR) 044. { 045. cout << "Bind failed with error:" << WSAGetLastError() << endl; 046. WSACleanup(); 047. return EXIT_FAILURE; 048. } 049. else 050. cout << "Bind success..." << endl; 051. 052. if (listen(listening, SOMAXCONN) == SOCKET_ERROR) 053. { 054. cout << "Listen failed with error:" << WSAGetLastError() << endl; 055. WSACleanup(); 056. return EXIT_FAILURE; 057. } 058. else 059. cout << "Listen success..." << endl; 060. 061. FD_ZERO(&master); 062. FD_SET(listening, &master); 063. cout << "Waiting for client connection" << endl; 064. 065. while (Looping) 066. { 067. fd_set tmp = master; 068. 069. ConnectCount = select(0, &tmp, nullptr, nullptr, nullptr); 070. for (int c0 = 0; c0 < ConnectCount; c0++) 071. { 072. if ((slave = tmp.fd_array[c0]) == listening) 073. { 074. SOCKET NewClient = accept(listening, nullptr, nullptr); 075. 076. szMsg = "Sent by SERVER: Welcome to Mini Chart\r\n"; 077. FD_SET(NewClient, &master); 078. send(NewClient, szMsg.c_str(), (int)szMsg.size() + 1, 0); 079. cout << "Client #" << NewClient << " connecting..." << endl; 080. } 081. else 082. { 083. char buff[DEFAULT_BUFLEN]; 084. int bytesIn; 085. 086. ZeroMemory(buff, DEFAULT_BUFLEN); 087. if ((bytesIn = recv(slave, buff, DEFAULT_BUFLEN, 0)) <= 0) 088. { 089. closesocket(slave); 090. cout << "Client #" << slave << " disconnected..." << endl; 091. FD_CLR(slave, &master); 092. } 093. else 094. { 095. if (buff[0] == '\\') //Check command ... 096. { 097. szMsg = string(buff, bytesIn); 098. 099. if (szMsg == "\\shutdown") 100. { 101. Looping = false; 102. break; 103. } 104. continue; 105. } 106. for (u_int c1 = 0; c1 < master.fd_count; c1++) 107. { 108. SOCKET out = master.fd_array[c1]; 109. 110. if ((out != listening) && (out != slave)) 111. { 112. ostringstream s1; 113. 114. s1 << "Client #" << slave << ": " << buff << "\r\n"; 115. send(out, s1.str().c_str(), (int)s1.str().size() + 1, 0); 116. } 117. } 118. } 119. 120. } 121. } 122. } 123. 124. FD_CLR(listening, &master); 125. closesocket(listening); 126. szMsg = "Server is shutting down. Goodbye\r\n"; 127. while (master.fd_count > 0) 128. { 129. slave = master.fd_array[0]; 130. send(slave, szMsg.c_str(), (int)szMsg.size() + 1, 0); 131. FD_CLR(slave, &master); 132. closesocket(slave); 133. } 134. 135. WSACleanup(); 136. 137. return EXIT_SUCCESS; 138. }
Source code of the service
One detail regarding this server code: although it is written in C++, you must compile it using the Visual Studio tools. However, if you understand what is being done, you can modify it so that it can be compiled using GCC. But do not become too attached to this code. Try to understand how it works, because that is what truly matters.
So I will explain some key points for those who are not very familiar with C++ and socket programming using C/C++. Lines 01 and 08 are related to the Visual Studio compiler. Line 10 defines the size of the buffer used to read from the socket. You may use a larger or smaller buffer, but keep in mind that if the buffer is too small, you will need to perform multiple reads in order to obtain the complete information from the socket. In any case, for what we are doing here (purely for didactic purposes) a buffer of 256 characters is sufficient.
Line 11 is indeed important for us, as it defines which port will be used. We could do this differently via a command-line argument, but the idea here is to keep things as simple as possible so the explanation can proceed without difficulty. Between lines 17 and 23, I declare some variables that will be used more frequently. Although I placed all the code inside the body of the main function, the ideal approach would be to split it into small routines. But since this server is extremely simple, we can afford to do everything within the main procedure.
Now observe something: at each step taken to configure the socket and allow the server to listen on a port, so that a connection from a client can occur, we print a message to the console. Thus, when the socket is created, we see confirmation of this on line 37. Note that two steps are required to do this. First, we call WSAStartup, and then the socket call itself. Systems such as Linux do not require the WSAStartup call, and even some socket implementations on Windows do not use it. However, in most cases it is good practice to use WSAStartup when working on Windows.
In any case, the socket will be created. Then, between lines 39 and 41, we configure it. This configuration specifies which port will be used, which address will be monitored, and the socket type. Since we are experimenting, we allow any address to connect. It is possible to restrict the address. But for servers, we normally allow all connections. Again, knowing how to configure this part is important, because if you do it incorrectly, you may end up opening the gate of a hell. Once this is done, we must inform the system how the socket will be configured. This occurs on line 43.
On line 52 we define the maximum number of simultaneous open connections that the server will accept. There is nothing complicated about this step. From this point on, the server is configured and ready to accept connections. We then prepare some structures to track the connections that will occur. This is done on lines 61 and 62. The idea is to create a type of dynamic array that will hold all open connections, allowing the server to operate as shown in the demo videos.
Once this is done, we enter the loop on line 65. This loop can be terminated by the client, as shown in the demo video. There are several things worth explaining within this loop. Most of it involves reading what one client posts to the socket and forwarding it to all other clients, which is not particularly noteworthy.
However, during the explanation of the MQL5 code, I mentioned that it is important to include a carriage return or newline character, and that the client did not add such characters – this was done by the server. Now that we are examining the server code, look at line 114, where we append the necessary characters so that the MQL5 client knows the message has been fully received. If this is not done here on the server, the mini chat implemented in MQL5 will not function properly.
This is the part you must ensure if you decide to implement your own server. You must guarantee that the server adds these same characters. The order does not matter, but they must appear at the end of the message.
Now the fun part begins. Earlier I mentioned that the client can shut down the server (this can be seen in the demonstration video). How does this happen? Simple. Look at line 95. There we define that a character at the beginning of the message will indicate a command for the server to execute. If this test succeeds, the command is analyzed and the posted message is not retransmitted to the other connected clients. Then, on line 99, we test one of these commands. It is case-sensitive, which means that the command must be provided exactly as expected by the server. If that happens, on line 101 we instruct the server to terminate its operation and close all open connections.
As you can see, everything is very simple. Nevertheless, remember that this is purely didactic code, since any client can send commands to the server. In a real server there would be authentication levels to prevent this, along with other security precautions that are unnecessary here.
Final Thoughts
In this article we conclude the presentation of how you can use external programming together with MQL5 to create a mini chat. Its purpose is purely educational, though it can be modified, improved, and even used among friends as the basis for something more professional. Make good use of this knowledge. In the attachments, you will find the mini chat MQL5 code already compiled and ready to use. All you will need to do is create the server. I prefer to leave this for you to implement yourself, since it involves opening ports on your computer. The program used in the video together with the mini chat is PuTTY.
| 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/12673
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.
Introduction to MQL5 (Part 35): Mastering API and WebRequest Function in MQL5 (IX)
Neural Networks in Trading: Two-Dimensional Connection Space Models (Final Part)
Price Action Analysis Toolkit (Part 55): Designing a CPI Mini-Candle Overlay for Intra-bar Pressure
Reimagining Classic Strategies (Part 21): Bollinger Bands And RSI Ensemble Strategy Discovery
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use