Market Simulation: Position View (VI)
Introduction
Welcome, everyone, to a new article in our series on the replay/simulation system.
In the previous article, Market Simulation: Position View (V), we explained how to obtain the data needed to understand what is happening in the application. This will allow us to implement the system using the OnTradeTransaction function. It only showed how to do this in the Expert Advisor. After looking through that data and the Expert Advisor, you have probably found the whole thing rather confusing and probably started wondering how practical it really is. A little skepticism will not hurt. It is also helpful—and even advisable—to have some doubts when we encounter something completely new.
Thus, in this article, we will take the first steps toward understanding the data presented in the previous article. Since the material we are covering may seem a little confusing at first glance, I want you to take your time later to go through this article and run some tests to try to understand what will be explained. Under no circumstances should you apply the material discussed in this article to a real account without fully understanding what is happening, as there is a high probability of incurring losses and making mistakes when interpreting the data displayed on the chart, which could lead to a very unpleasant experience when working with the system we are developing.
Once again: do not use the system we are going to create without fully understanding it. To do this, actively experiment and study the code, since its purpose here is to be as clear and educational as possible. In this case, efficiency is just a minor detail. The main requirement is that the code should be as simple as possible so that everyone can understand what is happening.
Let's take a look at what we are going to do.
Many of you are probably used to constantly seeing or performing the same tasks. It is not that this approach is wrong, but if there is another way, why not give it a try? This is precisely the point that needs to be understood here. The main purpose of the Expert Advisor is not to place objects on the chart or perform calculations. You can even do it this way. This is not an error. However, the Expert Advisor's main task is to provide a proper link to the trading server. Therefore, we can place only one Expert Advisor on each chart. Having the Expert Advisor handle tasks such as creating objects or working with them on the chart is not the best solution.
Let me emphasize this once again: there is absolutely nothing wrong with performing these tasks in the Expert Advisor. However, keep in mind that you may have more than one Expert Advisor, each designed for use in a specific scenario in a particular market. Putting everything into a single piece of code is not very practical, because if you decide to implement a position visualization system in each Expert Advisor and then improve it in another Expert Advisor, you will end up unhappy with using the old version, since you will have to transfer the code between all existing Expert Advisors.
That is why I suggest using an indicator for this. But as you have probably noticed from the latest articles, this indicator cannot do certain things. It is not possible—nor is it recommended—to attempt to force the indicator to perform specific tasks. However, we can create an indicator that will allow us to display open positions on the chart. We need to develop mechanisms that will allow us to use indicators in a more consistent and less confusing way.
One such mechanism is message exchange between the indicator and the Expert Advisor. We have been using this mechanism for some time now. This is because both the Chart Trade indicator and the Mouse Study indicator use the same mechanism to communicate with the Expert Advisor. But we have not yet created—or, more accurately, have not even begun developing—a mechanism that would allow the position indicator to interact with the Expert Advisor.
A small improvement
The first thing we should do is enable the Expert Advisor to add a position indicator to the chart every time a new position is opened. This is relatively simple to do, since we can use the same mechanism that is used when placing the Expert Advisor on the chart. If you look at the previous articles, you will notice that once the Expert Advisor is placed on the chart, it will search among the open positions for the one that matches the specified, expected criterion. This allows the Expert Advisor to correctly place the position indicator on the chart.
As shown in previous articles, removing the Expert Advisor from the chart will result in the removal of the position indicators placed on the chart. Good. This will go smoothly if the Expert Advisor is removed properly. If the program crashes, they will not be removed. Thus, when the Expert Advisor is run on the chart again, traces from its previous run may remain on the chart and will have to be removed. That leftover is precisely the position indicator left on the chart. Keep one thing in mind: we will assume that the trader has not touched any chart element. The trader simply noticed that the Expert Advisor had been removed from the chart, leaving some kind of position indicator on it.
Therefore, when the trader places the Expert Advisor on the chart again, the situation may have changed. Or, to be more precise: before the crash, the Expert Advisor was configured to monitor mini-contracts. However, when the trader puts it back on the chart, the Expert Advisor may be configured to monitor full contracts. However, some position indicators on the chart showed the presence of a mini-contract position. At this point, we may run into problems. Therefore, to avoid this, we need to add a line of code to the Expert Advisor's initialization procedure. This line will clear the chart of leftover indicators.
It is very simple and easy to understand. In the following excerpt, you can see how this is achieved.
15. //+------------------------------------------------------------------+ 16. C_Orders *Orders; 17. C_Terminal *Terminal; 18. //+------------------------------------------------------------------+ 19. int OnInit() 20. { 21. Terminal = NULL; 22. Orders = new C_Orders(0xC0DEDAFE78514269); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+ 27. void OnTick() {} 28. //+------------------------------------------------------------------+ 29. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 30. { 31. int handle; 32. 33. (*Orders).DispatchMessage(id, lparam, dparam, sparam); 34. switch (id) 35. { 36. case CHARTEVENT_CHART_CHANGE: 37. if (Terminal != NULL) break; 38. else 39. { 40. ulong ul; 41. Terminal = new C_Terminal(0, 0, user00); 42. for (int count = PositionsTotal() - 1; count >= 0; count--) 43. { 44. ul = PositionGetTicket(count); 45. if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) 46. { 47. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 48. continue; 49. } 50. handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul); 51. ChartIndicatorAdd(0, 0, handle); 52. IndicatorRelease(handle); 53. } 54. } 55. case CHARTEVENT_CUSTOM + evChartTrade_At_EA: 56. EventChartCustom(0, evEA_At_ChartTrade, user00, 0, ""); 57. break; 58. } 59. } 60. //+------------------------------------------------------------------+
Code snippet from an Expert Advisor
Please note line 47 of the previous snippet. It is precisely this that prevents what we have just described from happening. In other words, when the Expert Advisor is placed on the chart again, it will use line 47 to remove old indicators that may not meet the initialization criteria. In other words, if a trader changes the contract type, the Expert Advisor will detect this and remove all position indicators that do not match the expected results. If the indicator matches the contract type, no action will be needed. In the worst-case scenario, this will simply cause an error in the Expert Advisor, but it is acceptable and can be ignored.
Good. Now we have a certain consistency in the data that can be presented on the chart, and we can move on to the next step. It involves sending messages between the Expert Advisor and the position indicator.
Implementing Interaction Between the Expert Advisor and the Position Indicator
At this stage, we will begin the first exchange of information between these two applications. Since any message exchange involves many aspects that we need to analyze, we will do so step by step. Let's start with the basics and gradually delve into the details until we have a fully functional position indicator.
Since our position indicator currently contains only horizontal lines—so-called HLINE objects—we do not have to send many messages. But we must make some additional changes to the code. Starting with the Defines.mqh header file shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event To Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position //Event to communication 48. }; 49. //+------------------------------------------------------------------+ 50. enum EnumPriority { //Priority list on objects 51. ePriorityNull = -1, 52. ePriorityDefault = 0, 53. ePriorityChartTrade = 5, 54. ePriorityOrders = 10 55. }; 56. //+------------------------------------------------------------------+
Defines.mqh
In this file, we add a new enumeration that defines the event responsible for triggering a call to update the position indicator. This event is defined on line 47. Now that we have done that, we can move on to the Expert Advisor code. The complete code for the Expert Advisor is shown below:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Replay - EA.ico" 004. #property description "Demo version between interaction" 005. #property description "of Chart Trade and Expert Advisor" 006. #property version "1.118" 007. #property link "https://www.mql5.com/pt/articles/13203" 008. //+------------------------------------------------------------------+ 009. #include <Market Replay\Order System\C_Orders.mqh> 010. #include <Market Replay\Auxiliar\C_Terminal.mqh> 011. //+------------------------------------------------------------------+ 012. enum eTypeContract {MINI, FULL}; 013. //+------------------------------------------------------------------+ 014. input eTypeContract user00 = MINI; //Cross order in contract 015. //+------------------------------------------------------------------+ 016. C_Orders *Orders; 017. C_Terminal *Terminal; 018. //+------------------------------------------------------------------+ 019. int OnInit() 020. { 021. Terminal = NULL; 022. Orders = new C_Orders(0xC0DEDAFE78514269); 023. 024. return INIT_SUCCEEDED; 025. } 026. //+------------------------------------------------------------------+ 027. void OnTick() {} 028. //+------------------------------------------------------------------+ 029. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 030. { 031. int handle; 032. 033. (*Orders).DispatchMessage(id, lparam, dparam, sparam); 034. switch (id) 035. { 036. case CHARTEVENT_CHART_CHANGE: 037. if (Terminal != NULL) break; 038. else 039. { 040. ulong ul; 041. Terminal = new C_Terminal(0, 0, user00); 042. for (int count = PositionsTotal() - 1; count >= 0; count--) 043. { 044. ul = PositionGetTicket(count); 045. if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) 046. { 047. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 048. continue; 049. } 050. handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul); 051. ChartIndicatorAdd(0, 0, handle); 052. IndicatorRelease(handle); 053. } 054. } 055. case CHARTEVENT_CUSTOM + evChartTrade_At_EA: 056. EventChartCustom(0, evEA_At_ChartTrade, user00, 0, ""); 057. break; 058. } 059. } 060. //+------------------------------------------------------------------+ 061. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) 062. { 063. if (Terminal == NULL) return; 064. static ulong ticket = 0; 065. switch (trans.type) 066. { 067. case TRADE_TRANSACTION_HISTORY_ADD: 068. ticket = (trans.order != trans.position ? trans.position : 0); 069. break; 070. case TRADE_TRANSACTION_REQUEST: 071. if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action) 072. { 073. case TRADE_ACTION_DEAL: 074. if (ticket) EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 075. else 076. { 077. int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order); 078. ChartIndicatorAdd(0, 0, handle); 079. IndicatorRelease(handle); 080. } 081. ticket = 0; 082. break; 083. case TRADE_ACTION_SLTP: 084. EventChartCustom(0, evUpdate_Position, request.position, 0, ""); 085. break; 086. } 087. break; 088. }; 089. } 090. //+------------------------------------------------------------------+ 091. void OnDeinit(const int reason) 092. { 093. ulong ul; 094. 095. switch (reason) 096. { 097. case REASON_REMOVE: 098. case REASON_INITFAILED: 099. EventChartCustom(0, evEA_At_ChartTrade, -1, 0, ""); 100. break; 101. } 102. if (Terminal != NULL) for (int count = PositionsTotal() - 1; count >= 0; count--) 103. { 104. ul = PositionGetTicket(count); 105. if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue; 106. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 107. } 108. delete Orders; 109. delete Terminal; 110. } 111. //+------------------------------------------------------------------+
Expert Advisor.mq5
Most of this code has already been explained above. Therefore, we should focus on what's new here—that is, on the OnTradeTransaction function. For now, there is no need to worry about certain points that you may not yet understand. We will explain everything step by step. But here I want to specifically highlight the EventChartCustom function. This function appears on lines 74 and 84. It is these lines that allow the Expert Advisor to notify the position indicator that an event has occurred and that the position indicator needs to be updated.
For now, there is no need to worry about how this will be implemented in the indicator. Just observe when the Expert Advisor notifies the indicator that an update is needed. For a better understanding, you need to grasp the material covered in the previous article. If you have any questions about any of the information provided, please refer to the previous article.
Here, we will examine three different situations. The first of these occurs when, using the Chart Trade indicator, we ask the Expert Advisor to execute a market buy or a market sell. After that, the static variable ticket will have a value of zero. The reason is that line 68 will contain identical values for trans.order and trans.position. Good. Thus, when the server sends a TRADE_TRANSACTION_REQUEST notification, the corresponding handling for that operation will be performed. Note: at this point, the position is just being opened. Therefore, when the server generates TRADE_ACTION_DEAL, the check on line 74 will fail. And this happens because the ticket variable is zero. In this case, the code will be executed between lines 76 and 80. Please note that this is the same code that was used when initializing the Expert Advisor to tell MetaTrader 5 that we want to add a position indicator to the chart.
As a result, since we do not know why TRADE_ACTION_DEAL was triggered, we reset the ticket variable to zero. That is it. A position indicator will appear on the chart, displaying the same information we would see if MetaTrader 5 supported this mode. Let me remind you once again: the cross-order system is not fully supported in MetaTrader 5. For this reason, when we use this trading mode, we must create a way to display the data on the chart. That is what the position indicator is for.
So, now our position has been created and is displayed on the chart. After a while, the trader decides to increase the position volume. Then he will use the Chart Trade indicator again and ask it to increase the position volume. Here is an important point. On HEDGING accounts, this new opening or request to increase the position volume will not result in an actual change in volume. Here is what will actually happen: a new position will be opened. Since we do not distinguish between HEDGING and NETTING accounts, when a request is made to increase the position volume on a HEDGING account, the process is exactly the same as what I just explained for opening a position. However, when executing the same type of request—that is, increasing the position volume on a NETTING account—the situation will be slightly different. In this case, when the server generates the event TRADE_TRANSACTION_HISTORY_ADD, the values of trans.order and trans.position will differ. When this happens, it means that some change has occurred in the open position. Thus, the value of the ticket variable will no longer be zero. In this case, the ticket will be set to the corresponding position's ticket. At some point, the server will generate a TRADE_TRANSACTION_REQUEST event in response to the request it received.
When this happens and the TRADE_ACTION_DEAL action is processed, the check on line 74 will pass successfully. At that point, a custom event will be triggered, telling the position indicator that it needs to be updated. Please note that we have not bound this event to any specific indicator or application. All applications present on the chart will receive this same custom event. However, since we filter this event within each application, the message will be understood only by the correct indicator or application. At this stage, the Expert Advisor's work is complete. In reality, it has no idea whether the position indicator will understand this message or not. It does not even know whether this indicator is on the chart. That is of little concern to the Expert Advisor; it has done its job, and that is all. The same thing happens when the Take Profit and Stop Loss values for a position are changed. In this case, we will see the TRADE_ACTION_SLTP action being executed, which is located on line 83. Note that on line 84, we do the same thing as when increasing the position volume or performing a partial reduction of the position volume. This applies if you are using a NETTING account. "Okay.
But what happens when a position is closed?" I suppose, dear reader, you would expect to see code here, in the Expert Advisor, that is responsible for removing the position indicator from the chart. However, that is not exactly what you can see in the Expert Advisor's code. So how does it manage to remove this indicator from the chart? It is very simple. It does the same thing as when changing the volume in NETTING accounts. In other words, on line 68, the values of `trans.order` and `trans.position` will be different. This will allow the check on line 74 to pass successfully and will send a request to update the indicator. However, since the position has been closed, the indicator will be removed from the chart. To fully understand this, we need to take a look at the position indicator code. To keep the topics separate, we will cover them in a new section.
Improved position indicator
Now, you should know that the position indicator code currently consists of two parts. You can see the first example in its entirety below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Positions.ico" 04. #property description "Indicator for tracking an open position on the server." 05. #property description "This should preferably be used together with an Expert Advisor." 06. #property description "For more details see the same article." 07. #property version "1.118" 08. #property link "https://www.mql5.com/pt/articles/13203" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. #define def_ShortName "Position View" 13. //+------------------------------------------------------------------+ 14. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 15. #include <Market Replay\Defines.mqh> 16. //+------------------------------------------------------------------+ 17. input ulong user00 = 0; //For Expert Advisor use 18. input color user01 = clrRoyalBlue; //Color Line Price 19. input color user02 = clrForestGreen; //Color Line Take Profit 20. input color user03 = clrFireBrick; //Color Line Stop Loss 21. //+------------------------------------------------------------------+ 22. C_IndicatorPosition *Positions = NULL; 23. //+------------------------------------------------------------------+ 24. int OnInit() 25. { 26. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 27. Positions = new C_IndicatorPosition(user01, user02, user03); 28. if (!Positions.CheckCatch(user00)) 29. { 30. ChartIndicatorDelete(0, 0, def_ShortName); 31. return INIT_FAILED; 32. } 33. 34. return INIT_SUCCEEDED; 35. } 36. //+------------------------------------------------------------------+ 37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 38. { 39. return rates_total; 40. } 41. //+------------------------------------------------------------------+ 42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 43. { 44. (*Positions).DispatchMessage(id, lparam, dparam, sparam); 45. }; 46. //+------------------------------------------------------------------+ 47. void OnDeinit(const int reason) 48. { 49. delete Positions; 50. } 51. //+------------------------------------------------------------------+
Position View.mq5
This is the main indicator code. Note that, essentially, the only difference here is in the OnChartEvent function. Since we need some internal information from the C_IndicatorPosition class, we will not do anything in this main code. We simply call the DispatchMessage procedure. Therefore, simply by looking at this code, we have no way of understanding how the interaction with the Expert Advisor works. Let's now take a look at the header file C_IndicatorPosition.mqh, which is shown in full below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixTake "Take" 05. #define def_SufixStop "Stop" 06. //+------------------------------------------------------------------+ 07. #include "..\Auxiliar\C_Terminal.mqh" 08. //+------------------------------------------------------------------+ 09. class C_IndicatorPosition : private C_Terminal 10. { 11. private : 12. struct st00 13. { 14. ulong ticket; 15. color corPrice, corTake, corStop; 16. string szShortName; 17. }m_Infos; 18. //+------------------------------------------------------------------+ 19. inline void MoveLineInfos(string szObjName, const double price) 20. { 21. szObjName = m_Infos.szShortName + szObjName; 22. if (price <= 0) ObjectDelete(0, szObjName); 23. else ObjectSetDouble(0, szObjName, OBJPROP_PRICE, price); 24. } 25. //+------------------------------------------------------------------+ 26. void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n") 27. { 28. szObjName = m_Infos.szShortName + szObjName; 29. CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 30. ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription); 31. ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription); 32. ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 33. } 34. //+------------------------------------------------------------------+ 35. public : 36. //+------------------------------------------------------------------+ 37. C_IndicatorPosition(color corPrice, color corTake, color corStop) 38. :C_Terminal() 39. { 40. ZeroMemory(m_Infos); 41. m_Infos.corPrice = corPrice; 42. m_Infos.corTake = corTake; 43. m_Infos.corStop = corStop; 44. } 45. //+------------------------------------------------------------------+ 46. ~C_IndicatorPosition() 47. { 48. if (m_Infos.ticket != 0) 49. ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket)); 50. } 51. //+------------------------------------------------------------------+ 52. bool CheckCatch(ulong ticket) 53. { 54. m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket); 55. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 56. if (ObjectFind(0, m_Infos.szShortName) >= 0) 57. { 58. m_Infos.ticket = 0; 59. return false; 60. } 61. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 62. CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price."); 63. CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point."); 64. CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point."); 65. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 66. 67. return true; 68. } 69. //+------------------------------------------------------------------+ 70. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 71. { 72. switch (id) 73. { 74. case CHARTEVENT_CUSTOM + evUpdate_Position: 75. if (lparam != m_Infos.ticket) return; 76. if (!PositionSelectByTicket(m_Infos.ticket)) 77. { 78. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 79. return; 80. }; 81. MoveLineInfos(NULL, PositionGetDouble(POSITION_PRICE_OPEN)); 82. MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP)); 83. MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL)); 84. break; 85. } 86. } 87. //+------------------------------------------------------------------+ 88. }; 89. //+------------------------------------------------------------------+ 90. #undef def_SufixTake 91. #undef def_SufixStop 92. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Although you might think everything should be done in one specific way, here we will implement it somewhat differently from what many people are used to. In fact, we have not made many changes to the code at this stage, but you can notice that there are different elements here. To begin with, on line 19 we have a new procedure. Its purpose is to change the point where the horizontal price line is located. There is not much to say about this procedure, since when the price is zero or negative, the line object will be deleted. This is done on line 22 of the code shown above.
However, it is worth remembering that although a negative price is usually not possible, this does not apply to indices—or, more precisely, when you use a combination of two or more assets to generate a new value, negative values may well occur. But this value refers not to a negative price, but to a negative index or coefficient. If you have problems using charts based on value interactions, you will need to adjust this position indicator. At this stage, we are focused on prices rather than ratios. Later, I will show you how to work with relationships in MetaTrader 5.
But let's take it one step at a time. To begin, we will discuss how to get the position indicator to work. "Okay. If the price is valid, on line 23 we will tell the HLINE object where it should be located. It is simple. Now we will consider an interesting moment concerning the function on line 52. Note that the custom event is triggered on line 65. But why is it done this way? The reason is simple: write it once, test it repeatedly. Since this indicator will receive custom messages or events, we can configure it so that, when placed on a chart, it behaves the same way as when it receives an update request from an Expert Advisor.
As I pointed out in the previous section, the Expert Advisor will not check whether the position indicator has performed its function or not. It simply generates an event and moves on to other tasks. The position indicator is responsible for carrying out all remaining tasks. Therefore, it is advisable to have a clear implementation methodology. If the position indicator works properly when added manually to the chart but cannot interpret the Expert Advisor's requests, you can quickly conclude that there is a communication failure between the Expert Advisor and the indicator. We can draw this conclusion because events are handled properly when the indicator is added to the chart manually.
Thus, just as an Expert Advisor triggers an event so that the position indicator can perform its task, the position indicator itself will also trigger a similar event to check whether it can be placed on the chart. This means that in both situations, the function will be called from line 70 of the code shown above.
The code on line 70 is quite interesting. For now, we are only interested in handling one event: the evUpdate_Position event, which starts being handled on line 74. On line 75, we check whether the position ticket specified in the event call matches the one expected by the indicator. If the tickets differ, processing stops at this stage. It is important to verify that the ticket number matches, since the event notification is sent to all applications on the chart. That is why it is important to filter events properly before using the data. Once the filtering is complete, we can check on line 76 whether this position exists. Remember: the Expert Advisor does not tell us whether we are updating, closing, or opening a new position. It simply says: position indicator, I have something for you. Do whatever needs to be done. And that's it.
The indicator is responsible for the rest; it must perform and verify all necessary actions. If there is no position, we stop the indicator on line 78, and it is removed from the chart. However, if a position is created or updated, the price lines will shift. This is done on lines 81 through 83. Here is how this mechanism works. However, there is a problem with the code shown above. It is important to understand this before trying to work through the code we will cover in the next section.
Is that really how it has to be?
An issue has been found in the code of the C_IndicatorPosition class shown above. Actually, it is more of an inconvenience than a problem. However, the catch is that if you remove or choose not to add the Stop Loss and Take Profit lines, and then decide to add them to the chart later, the position will be updated, and you will not be able to do this directly. The class discussed earlier cannot do this. However, by making a few changes to the code, we can start doing that. These changes are shown in the code below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixTake "Take" 05. #define def_SufixStop "Stop" 06. //+------------------------------------------------------------------+ 07. #include "..\Auxiliar\C_Terminal.mqh" 08. //+------------------------------------------------------------------+ 09. class C_IndicatorPosition : private C_Terminal 10. { 11. private : 12. struct st00 13. { 14. ulong ticket; 15. color corPrice, corTake, corStop; 16. string szShortName; 17. }m_Infos; 18. //+------------------------------------------------------------------+ 19. inline void MoveLineInfos(const string szDefine, const double price) 20. { 21. string szName = m_Infos.szShortName + szDefine; 22. if (price <= 0) ObjectDelete(0, szName); 23. else 24. { 25. if (ObjectFind(0, szName) < 0) 26. { 27. if (szDefine == def_SufixTake) CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point."); else 28. if (szDefine == def_SufixStop) CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point."); else 29. CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price."); 30. } 31. ObjectSetDouble(0, szName, OBJPROP_PRICE, price); 32. } 33. } 34. //+------------------------------------------------------------------+ 35. void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n") 36. { 37. szObjName = m_Infos.szShortName + szObjName; 38. CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 39. ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription); 40. ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription); 41. ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 42. } 43. //+------------------------------------------------------------------+ 44. public : 45. //+------------------------------------------------------------------+ 46. C_IndicatorPosition(color corPrice, color corTake, color corStop) 47. :C_Terminal() 48. { 49. ZeroMemory(m_Infos); 50. m_Infos.corPrice = corPrice; 51. m_Infos.corTake = corTake; 52. m_Infos.corStop = corStop; 53. } 54. //+------------------------------------------------------------------+ 55. ~C_IndicatorPosition() 56. { 57. if (m_Infos.ticket != 0) 58. ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket)); 59. } 60. //+------------------------------------------------------------------+ 61. bool CheckCatch(ulong ticket) 62. { 63. m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket); 64. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 65. if (ObjectFind(0, m_Infos.szShortName) >= 0) 66. { 67. m_Infos.ticket = 0; 68. return false; 69. } 70. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 71. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 72. 73. return true; 74. } 75. //+------------------------------------------------------------------+ 76. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 77. { 78. switch (id) 79. { 80. case CHARTEVENT_CUSTOM + evUpdate_Position: 81. if (lparam != m_Infos.ticket) return; 82. if (!PositionSelectByTicket(m_Infos.ticket)) 83. { 84. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 85. return; 86. }; 87. MoveLineInfos(NULL, PositionGetDouble(POSITION_PRICE_OPEN)); 88. MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP)); 89. MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL)); 90. break; 91. } 92. ChartRedraw(); 93. } 94. //+------------------------------------------------------------------+ 95. }; 96. //+------------------------------------------------------------------+ 97. #undef def_SufixTake 98. #undef def_SufixStop 99. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
The code shown above should replace the original code discussed in the previous section. As you can see, there were only a few changes. But they are sufficient for the position indicator to handle the situation described at the beginning of this section. Let's see what has changed compared to what was done before.
In fact, in the function shown on line 61, we no longer create HLINE objects. The lines of code that caused this result have been removed. In fact, they were not destroyed; they were moved. Note that all the code remains unchanged. However, if we go to line 19, we will see something very strange that had not happened before.
Previously, when we requested that a price line be moved, the code simply instructed the object to move. But now there is an extra step. This is on line 25. This step allows us to recreate the line if necessary. In other words, when a trader deletes a line, the Expert Advisor instructs the position indicator to update the data. The indicator will delete the same line that the trader deleted. However, when the trader sets the line again, indicating the price that will now be used as the Stop Loss or Take Profit, the Expert Advisor will instruct the position indicator to update the data once more. At this stage, when line 25 is executed, the position indicator will detect that the HLINE object does not exist and will create it appropriately for the new position state. Only then will the position indicator place the line in the correct spot. This is done using line 31.
You can see what I have just explained in the following video, which makes it clear how this position indicator currently works.
Concluding Thoughts
In this article, we made a number of improvements so that the position indicator reflects the actual situation on the trading server, both in terms of positions and their current state. Note that the applications presented here are in no way intended to replace any elements available in MetaTrader 5. They should also not be used without due caution and without following the necessary criteria, since their purpose is to provide educational code—that is, code for instructional purposes, to teach how everything works.
The reason I call this code educational is that, in some cases, using messages is not the best way to implement certain functions. Please note that the position indicator placed on the chart generates a message to which it will respond. This feature somewhat reduces the code's responsiveness, since we are adding a call where it is not needed.
In any case, I do not plan on changing the way the code is presented. If you want to try out this code but are not sure how to create all the files (even though the full code is provided here), you can find the precompiled applications in the attached files. This way, all you need to do is use them in MetaTrader 5 and see how the message exchange works. These applications have been tested on HEDGING and NETTING accounts, and they work flawlessly. You do not have to change anything; just unzip the attached file into the MQL5 folder. You will get the same result as shown in the video.
| File | Description |
|---|---|
| Experts\Expert Advisor.mq5 | Shows the interaction between Chart Trade and the Expert Advisor on the chart. (Mouse Study is required for this interaction.) |
| Indicators\Chart Trade.mq5 | Creates a window for configuring the order submission settings. (Mouse Study is required for the interaction.) |
| Indicators\Market Replay.mq5 | Creates controls for interacting with the replay/simulation service. (Mouse Study is required for the interaction.) |
| Indicators\Mouse Study.mq5 | Provides interaction between the graphical controls and the user. (This is necessary both for the replay/simulation system and in the real market.) |
| Indicators\Order Indicator.mq5 | Responsible for displaying market orders, as well as allowing interaction with and control over them. |
| Indicators\Position View.mq5 | Responsible for displaying market positions, allowing interaction with and control over them. |
| Services\Market Replay.mq5 | Creates and maintains the market replay/simulation service. (Main system file.) |
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/13203
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.
How to Create and Adapt an RL Agent with an LLM and Quantum Encoding for Algorithmic Trading in MQL5
The MQL5 Standard Library Explorer (Part 15): Building a Market-Regime Classifier with dataanalysis.mqh
Honest Backtesting of Swing Strategies on Index CFDs: Financing Costs, Swap Modes, and What the Strategy Tester Cannot Model
Building a Prop-Firm Compliance Monitor in MQL5 (Part 1): Account Rules and Persistent Settings
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use