Português
preview
Simulación de mercado: Position View (XVII)

Simulación de mercado: Position View (XVII)

MetaTrader 5Probador |
32 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos y bienvenidos a otro artículo de la serie sobre la construcción de un sistema de repetición/simulación.

En el artículo anterior, Simulación de mercado: Position View (XVI), hicimos que el indicador de posición mostrara el resultado financiero de una posición abierta. Sin embargo, no a todos los operadores les gusta utilizar este modo de visualización. Los motivos pueden variar, aunque en algunos casos el motivo me parece bastante plausible y justificable: evitar que la codicia o el miedo alcancen niveles excesivos durante una operación. Al observar importes monetarios, algunos operadores temen perder todavía más o devolver al mercado las ganancias que ya han obtenido. En determinados momentos, este tipo de situación perjudica considerablemente al operador. Por eso, algunos operadores prefieren alejarse y evitar mirar una posición abierta, con el fin de evitar la tentación de cerrarla antes de tiempo.

Aunque esta actitud sea justificable, no es adecuada, ya que algunos símbolos pueden atravesar periodos de gran volatilidad. En esas circunstancias, las órdenes Stop Loss y Take Profit pueden ejecutarse fuera de los niveles previstos, lo que puede hacer que una operación arroje resultados inesperados. Cuando el Take Profit es mayor de lo esperado, no veo a nadie quejarse en las redes sociales ni en otras plataformas de comunicación. Sin embargo, cuando la pérdida supera lo planificado, todos empiezan a insultar y a hablar mal de todo lo que existe o pueda llegar a existir. En estos casos, la responsabilidad recae exclusivamente en el operador, que debería haberse mantenido atento por tener una posición abierta durante un periodo de elevada volatilidad.

No obstante, no estoy aquí para debatir esta cuestión. Solo quería exponer por qué debemos ofrecer distintos métodos para visualizar los datos de una posición, ya que un operador puede no sentirse cómodo con uno u otro.

Adaptar el código para ofrecer esta posibilidad no es, ni mucho menos, una de las tareas más complejas. En realidad, se trata de algo bastante sencillo, pero también interesante de explicar. Puede resultar muy útil para los entusiastas que utilizan estos artículos como material de apoyo para estudiar y aprender un poco más sobre MQL5. Así que vayamos directamente al grano.


Creación de un sistema de intercambio

Muy bien. Antes de comenzar, veamos el código de la clase C_ElementsTrade, para que puedas situarte en el estado actual del código. A continuación aparece el código completo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_NameHLine       m_Info.szPrefixName + "#HLINE"
005. #define def_NameBtnClose    m_Info.szPrefixName + "#CLOSE"
006. #define def_NameBtnMove     m_Info.szPrefixName + "#MOVE"
007. #define def_NameInfoDirect  m_Info.szPrefixName + "#DIRECT"
008. #define def_NameObjLabel    m_Info.szPrefixName + "#PROFIT"
009. #define def_NameBackGround  m_Info.szPrefixName + "#BACKGROUND"
010. //+------------------------------------------------------------------+
011. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
012. //+------------------------------------------------------------------+
013. #define def_PathBtns "Images\\Market Replay\\Orders\\"
014. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
015. #resource "\\" + def_Btn_Close;
016. //+------------------------------------------------------------------+
017. #include "..\Auxiliar\C_Mouse.mqh"
018. //+------------------------------------------------------------------+
019. #ifdef def_FontName
020.     "Why are you trying to do this?"
021. #else 
022.     #define def_FontName "Lucida Console"
023.     #define def_FontSize 10
024. #endif 
025. //+------------------------------------------------------------------+
026. class C_ElementsTrade : private C_Mouse
027. {
028.     private    :
029. //+------------------------------------------------------------------+
030.         struct st00
031.         {
032.             struct st_01
033.             {
034.                 short   Width,
035.                         Height,
036.                         digits;
037.             }Text;
038.             ulong       ticket;
039.             string      szPrefixName,
040.                         szDescr;
041.             EnumEvents  ev;
042.             double      price,
043.                         open,
044.                         volume,
045.                         var;
046.             bool        bClick,
047.                         bIsBuy;
048.             char        weight;
049.             color       _color;
050.             int         sizeText;
051.         }m_Info;
052. //+------------------------------------------------------------------+
053.         short UpdateViewPort(const double price, short size = 0, uint ui = 0)
054.         {
055.             static short _SizeControls;
056.             static short _Width;
057.             uint x, y;
058.             
059.             if (size > 0)
060.             {
061.                 size += (short)(ui + 8);
062.                 _SizeControls = (_SizeControls > size ? _SizeControls : size);
063.                 size = (short)(_SizeControls - ui - 12);
064.                 _Width = (_Width > size ? _Width : size);
065.             }else
066.             {
067.                 ChartTimePriceToXY(0, 0, 0, price, x, y);            
068.                 x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2)));
069.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
070.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
071.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
072.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
073.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
074.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
075.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + _Width + 20);
076.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
077.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20);
078.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
079.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
080.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
081.             }
082.             return _Width;
083.         }
084. //+------------------------------------------------------------------+
085. inline void CreateLinePrice(void)
086.         {
087.             string szObj;
088.             
089.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
090.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
091.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
092.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
093.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
094.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
095.             macro_LineInFocus(false);
096.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
097.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
098.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
099.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
100.         }
101. //+------------------------------------------------------------------+
102. inline void CreateBoxInfo(const bool bMove)
103.         {
104.             string szObj;
105.             const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0};
106.             
107.             CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
108.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
109.             ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c));
110.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon)));
111.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15));
112.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
113.         }
114. //+------------------------------------------------------------------+
115. inline void CreateObjectInfoText(void)
116.         {
117.             string szObj;
118.             
119.             CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault));
120.             ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName);
121.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize);
122.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack);
123.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color);
124.             ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER);
125.             ObjectSetInteger(0, szObj, OBJPROP_READONLY, true);
126.         }
127. //+------------------------------------------------------------------+
128. inline void CreateButtonClose(void)
129.         {
130.             string szObj;
131.             
132.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
133.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
134.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
135.         }
136. //+------------------------------------------------------------------+
137. inline void AdjustDinamic(const string szTxt)
138.         {
139.             uint w, h;
140. 
141.             TextSetFont(def_FontName, def_FontSize * -10);
142.             TextGetSize(szTxt, w, h);
143.             m_Info.Text.Height = (uchar) h + 4;
144.             m_Info.Text.Width = (uchar) w + 4;
145.             m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32);
146.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XSIZE, m_Info.Text.Width);
147.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YSIZE, m_Info.Text.Height);
148.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? 8 : 0));
149.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5);
150.         }
151. //+------------------------------------------------------------------+
152.     public    :
153. //+------------------------------------------------------------------+
154.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true)
155.             :C_Mouse(0, "")
156.         {
157.             ZeroMemory(m_Info);
158.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
159.             m_Info._color = _color;
160.             m_Info.szDescr = szDescr;
161.             m_Info.bIsBuy = IsBuy;
162.             m_Info.Text.digits = digits;    
163.         }
164. //+------------------------------------------------------------------+
165.         ~C_ElementsTrade()
166.         {
167.             ObjectsDeleteAll(0, m_Info.szPrefixName);
168.         }
169. //+------------------------------------------------------------------+
170. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
171.         {
172.             m_Info.sizeText = 0;
173.             ObjectsDeleteAll(0, m_Info.szPrefixName);
174.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
175.             m_Info.var = (var > 0 ? var : m_Info.var);
176.             if (price > 0)
177.             {
178.                 CreateLinePrice();
179.                 CreateButtonClose();
180.                 CreateObjectInfoText();
181.             }
182.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
183.             m_Info.open = open;
184.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
185.             if (m_Info.ev != evMsgClosePositionEA)
186.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
187.         }
188. //+------------------------------------------------------------------+
189.         void ViewValue(const double profit)
190.         {
191.             string szTxt;
192.             
193.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
194.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
195.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
196.             if (StringLen(szTxt) != m_Info.sizeText)
197.             {
198.                 AdjustDinamic(szTxt);
199.                 m_Info.sizeText = StringLen(szTxt);
200.             }
201.         }
202. //+------------------------------------------------------------------+
203.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
204.         {
205.             string sz0;
206.             long _lparam = lparam;
207.             double _dparam = dparam;
208.             
209.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
210.             switch (id)
211.             {
212.                 case (CHARTEVENT_KEYDOWN):
213.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
214.                     _lparam = (long) m_Info.ticket;
215.                     _dparam = 0;
216.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
217.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
218.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
219.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
220.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
221.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
222.                     m_Info.bClick = false;
223.                 case CHARTEVENT_CHART_CHANGE:
224.                     UpdateViewPort(m_Info.price);
225.                     if (m_Info.ev != evMsgClosePositionEA)
226.                         ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
227.                     break;
228.                 case CHARTEVENT_OBJECT_CLICK:
229.                     sz0 = GetPositionsMouse().szObjNameClick;
230.                     if (m_Info.bClick)
231.                     {
232.                         if (sz0 == def_NameBtnMove)
233.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
234.                         if (sz0 == def_NameBtnClose)
235.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL));
236.                     }
237.                     m_Info.bClick = false;
238.                     break;
239.                 case CHARTEVENT_MOUSE_MOVE:
240.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
241.                     if (m_Info.weight > 1)
242.                     {
243.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
244.                         if (m_Info.ev != evMsgClosePositionEA)
245.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
246.                         if (m_Info.bClick)
247.                         {
248.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
249.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
250.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
251.                         }
252.                     }
253.                     break;
254.             }
255.         }
256. //+------------------------------------------------------------------+
257. };
258. //+------------------------------------------------------------------+
259. #undef macro_LineInFocus
260. //+------------------------------------------------------------------+
261. #undef def_Btn_Close
262. #undef def_PathBtns
263. #undef def_FontName
264. #undef def_FontSize
265. //+------------------------------------------------------------------+
266. #undef def_NameBackGround
267. #undef def_NameObjLabel
268. #undef def_NameInfoDirect
269. #undef def_NameBtnMove
270. #undef def_NameBtnClose
271. #undef def_NameHLine
272. //+------------------------------------------------------------------+

C_ElementsTrade

¿Por qué lo muestro íntegramente? Debido a la complejidad que implica compilar todo el sistema, he ido mostrando todas las modificaciones que ha experimentado el código. De este modo, quienes deseen estudiar el proyecto o incluso modificarlo tendrán acceso al código fuente. Se trata de un proyecto extenso, compuesto por varios archivos y aplicaciones independientes que deben funcionar en conjunto, por lo que muchos podrían tener dificultades para comprender cómo funciona todo el sistema. Por ese motivo, en los archivos adjuntos al artículo solo incluyo los ejecutables. Sin embargo, quienes sigan la serie y deseen disponer del código fuente podrán obtenerlo completo a partir de lo que voy mostrando, artículo por artículo.

Una vez aclarado esto, comenzaremos con un pequeño cambio o, mejor dicho, añadiremos algo al código. La modificación aparece en el siguiente fragmento.

25. //+------------------------------------------------------------------+
26. class C_ElementsTrade : private C_Mouse
27. {
28.     private    :
29. //+------------------------------------------------------------------+
30.         struct stInfos
31.         {
32.             struct st_01
33.             {
34.                 short   Width,
35.                         Height,
36.                         digits;
37.             }Text;
38.             ulong       ticket;
39.             string      szPrefixName,
40.                         szDescr;
41.             EnumEvents  ev;
42.             double      price,
43.                         open,
44.                         volume,
45.                         var,
46.                         tickSize;
47.             bool        bClick,
48.                         bIsBuy;
49.             char        weight;
50.             color       _color;
51.             int         sizeText;
52.             enum {eValue, eFinance, eTicks, ePercentage} ViewMode;
53.         }m_Info;
54. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que en la línea 30 se ha modificado el nombre de la estructura. Lo más curioso aparece en la línea 51. Esta construcción puede parecer extravagante e incluso disparatada, ya que probablemente no hayas visto antes algo similar. Veamos con más detalle la línea 52. A medida que avancemos en el artículo, el código quedará más claro y comprenderás por qué se ha definido de esta manera. En primer lugar, tenemos una enumeración anónima, es decir, una enumeración sin nombre. Esto puede parecer extraño, pero, como al final declaramos una variable de ese tipo, podremos utilizar sin problemas los valores definidos en la enumeración. No obstante, la principal duda probablemente sea otra: ¿por qué se encuentra esta enumeración dentro de la estructura y qué implicaciones tendrá en el código?

Al declararla dentro de la estructura, el ámbito de la enumeración queda restringido a la estructura. Esta técnica permite crear enumeraciones cuyos valores tengan nombres similares. Una de las mayores dificultades al programar no es escribir el código, sino asignar nombres adecuados. Puedes acabar teniendo muchos más problemas para recordar qué representa una variable llamada value_001 que para comprender qué significa otra llamada Price_01. Observa que el nombre de la variable declarada en la línea 52 parece fácil de interpretar. Sin embargo, cuando empieces a programar e implementar todo esto, comprobarás que ese nombre puede dejar de ser suficientemente descriptivo. Lo mismo puede suceder con los valores de la enumeración. Al estar la enumeración contenida en la estructura, puedo asignarle un significado mediante su pertenencia a la estructura. De este modo, la variable declarada en la línea 52 adquiere un sentido específico dentro de esa estructura.

Una vez hecho esto, tenemos que modificar un detalle en el código principal. Para mostrar qué se modificará, veamos el fragmento que aparece a continuación.

018. //+------------------------------------------------------------------+
019. struct st00
020. {
021.     ulong     ticket;
022.     string    szShortName,
023.               szSymbol;
024.     double    priceOpen,
025.               var,
026.               tickSize;
027.     char      digits;
028.     bool      bIsBuy;
029. }m_Infos;
030. //+------------------------------------------------------------------+
031. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL;
032. //+------------------------------------------------------------------+
033. bool CheckCatch(ulong ticket)
034. {
035.     double vv;
036.     
037.     ZeroMemory(m_Infos);
038.     m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket);
039.     if (!PositionSelectByTicket(m_Infos.ticket)) return false;
040.     if (ObjectFind(0, m_Infos.szShortName) >= 0)
041.     {
042.         m_Infos.ticket = 0;
043.         return false;
044.     }
045.     m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL);
046.     m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
047.     m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
048.     vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
049.     m_Infos.var = m_Infos.tickSize / vv;
050.     IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
051.     EventChartCustom(0, evUpdate_Position, ticket, 0, "");
052.             
053.     return true;
054. }
055. //+------------------------------------------------------------------+
                                   .
                                   .
                                   .
084. //+------------------------------------------------------------------+
085. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
086. {
087.     double volume;
088.     
089.     if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
090.     if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
091.     if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam);
092.     switch (id)
093.     {
094.         case CHARTEVENT_CUSTOM + evUpdate_Position:
095.             if (lparam != m_Infos.ticket) break;
096.             if (!PositionSelectByTicket(m_Infos.ticket))
097.             {
098.                 ChartIndicatorDelete(0, 0, m_Infos.szShortName);
099.                 return;
100.             };
101.             if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, m_Infos.digits, m_Infos.tickSize, StringFormat("%I64u : Position opening price.", m_Infos.ticket), m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY));
102.             if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, m_Infos.digits, m_Infos.tickSize, StringFormat("%I64u : Take Profit price.", m_Infos.ticket), m_Infos.bIsBuy);
103.             if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, m_Infos.digits, m_Infos.tickSize, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), m_Infos.bIsBuy);
104.             volume = PositionGetDouble(POSITION_VOLUME);
105.             (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var);
106.             (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var);
107.             (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var);
108.             ProfitNow();
109.             break;
110.     }
111.     ChartRedraw();
112. };
113. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que en la línea 26 añadimos una nueva variable, a la que asignamos un valor en la línea 47. Realizamos esta operación aquí para evitar efectuarla innecesariamente dentro de la clase C_ElementsTrade. Después, el valor se transfiere a la clase en las líneas 101 a 103. Este valor servirá de apoyo para el formato con el que, por ahora, mostraremos la cantidad de ticks. No lo confundas con un valor en puntos, pues no son lo mismo.

Como probablemente ya habrás deducido, también debemos modificar el constructor de la clase. El cambio aparece en el siguiente fragmento.

155. //+------------------------------------------------------------------+
156.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true)
157.             :C_Mouse(0, "")
158.         {
159.             ZeroMemory(m_Info);
160.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
161.             m_Info._color = _color;
162.             m_Info.szDescr = szDescr;
163.             m_Info.bIsBuy = IsBuy;
164.             m_Info.Text.digits = digits;
165.             m_Info.tickSize = ticksize;
166.         }
167. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que las modificaciones son muy sencillas, por lo que no voy a destacarlas aquí. Podemos pasar directamente al procedimiento que genera los valores mostrados y adaptarlo para aplicar el formato correspondiente. Veamos, por tanto, el nuevo código del procedimiento ViewValue, que aparece a continuación.

191. //+------------------------------------------------------------------+
192.         void ViewValue(const double profit)
193.         {
194.             string szTxt;
195.             
196.             switch (m_Info.ViewMode)
197.             {
198.                 case stInfos::eValue:
199.                     szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f ", MathAbs(profit));
200.                     break;
201.                 case stInfos::eFinance:
202.                     szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f ", (MathAbs(profit) / m_Info.var) * m_Info.volume);
203.                     break;
204.                 case stInfos::eTicks:
205.                     szTxt = StringFormat(" %d ", (uint)MathRound(MathAbs(profit) / m_Info.tickSize));
206.                     break;
207.                 case stInfos::ePercentage:
208.                     szTxt = StringFormat(" %.2f%% ", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2));
209.                     break;
210.             }
211.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
212.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
213.             if (StringLen(szTxt) != m_Info.sizeText)
214.             {
215.                 AdjustDinamic(szTxt);
216.                 m_Info.sizeText = StringLen(szTxt);
217.             }
218.         }
219. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Aquí tenemos un código especialmente elegante por la sencillez con la que permite conseguir el resultado deseado. Observa que en la línea 194 iniciamos una selección mediante switch entre los modos declarados en la línea 52. De forma predeterminada, cuando se ejecute el constructor, se activará el modo cuyo valor sea cero. Si quieres utilizar el modo financiero como modo predeterminado, colócalo como primer elemento de la enumeración de la línea 52.

Ahora observa que en las líneas 198, 201, 204 y 207 se comprueba qué caso corresponde para aplicar el formato adecuado al valor que se mostrará. Fíjate en que no basta con indicar el valor de la enumeración. También debemos escribir el nombre de la estructura seguido de un doble signo de dos puntos (::). Este símbolo es el operador de resolución de ámbito y permite que el compilador determine a qué enumeración pertenece el valor. Si tuvieras varias enumeraciones con elementos que tengan nombres similares, identificarlas podría resultar confuso. Sin embargo, al utilizar un diseño algo mejor, resulta más sencillo localizar la enumeración correcta. Este es el motivo por el que cambié el nombre de la estructura.

El código anterior resuelve por completo el problema de mostrar los valores en formatos diferentes. No obstante, todavía queda una cuestión: ¿cómo permitimos que el operador o usuario cambie la información mostrada de la forma más sencilla posible? No queremos complicarnos innecesariamente la tarea de programación ni mucho menos confundir al usuario cuando intente leer la información mostrada.

Existen varias formas de hacerlo. Algunas son muy sencillas y otras algo más complejas. Sin embargo, debes recordar siempre lo siguiente: para el usuario, la opción más sencilla consiste en hacer clic en algún elemento. Para quienes programamos, en cambio, sería más fácil que el usuario modificara un valor definido como parámetro de entrada del indicador.

Los parámetros de entrada a los que me refiero aparecen en la siguiente imagen.

Utilizar los parámetros que aparecen en la imagen anterior sería, con diferencia, la forma más sencilla de programar esta solución. Sin embargo, para el operador o usuario no resulta demasiado intuitivo ni práctico, ya que tendría que abrir la configuración del indicador y modificar un valor para que se produjera el cambio. En cambio, hacer clic en algún elemento del indicador o en un objeto del gráfico sí es una acción completamente intuitiva. Por tanto, utilizaremos este enfoque, aunque implique algo más de trabajo para nosotros como programadores.

Este enfoque plantea nuevos problemas y exige tomar algunas decisiones. Una de estas decisiones afecta al uso del indicador de posición en cuentas de tipo HEDGING, pues en ese caso puede haber varios indicadores de posición en el gráfico. Otra decisión se refiere a cómo deberán funcionar las cosas. Para que comprendas mejor esta segunda cuestión —la primera se resolverá durante la implementación—, supongamos que el indicador se encuentra en el modo valor.

Ahora imaginemos que el usuario desea visualizar el take profit como resultado financiero. Al hacer clic en él, ¿solo el take profit cambiará al modo financiero o también lo harán los demás valores, es decir, el precio de apertura y el stop loss? ¿Y qué sucede si quiere mostrar el take profit como resultado financiero, el precio de apertura como porcentaje y el stop loss como diferencia entre precios? ¿Permitiremos esta combinación? Observa que todos estos aspectos deben definirse antes de comenzar a implementar la solución. Otra cuestión es dónde tendrá que hacer clic el usuario u operador para cambiar el formato del valor. Fíjate en que, aunque la codificación resulte sencilla, analizar estos puntos forma parte de la implementación. Si decides modificar el comportamiento más adelante, podrías tener más o menos trabajo para conseguirlo.

Veamos entonces mi propuesta. Puedes discrepar de ella. Es tu derecho. De todos modos, aquí la intención es mostrar cómo se llega a un resultado que se va a implementar. Mi propuesta es que el usuario haga clic en el objeto OBJ_EDIT para cambiar el formato. El formato cambiará de manera cíclica. Es decir, cada vez que el usuario haga clic en OBJ_EDIT, se seleccionará el siguiente modo definido en la enumeración.

Con esto acabo de darte una pista sobre el próximo procedimiento que modificaremos. Y sí, es exactamente el que probablemente has imaginado: el procedimiento DispatchMessage de la clase C_ElementsTrade.

Antes haremos otro cambio, que aparece a continuación.

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.             evMsgClosePositionEA,           //Event to communication
49.             evMsgCloseTakeProfit,           //Event to communication
50.             evMsgCloseStopLoss,             //Event to communication
51.             evMsgNewTakeProfit,             //Event to communication
52.             evMsgNewStopLoss,               //Event to communication
53.             evMsgSetFocus,                  //Event to communication
54.             evMsgServiceSwapStatus,         //Event to stop the replay/simulator service
55.             evMsgSwapViewModePosition       //Event to Swap Mode
56.                         };
57. //+------------------------------------------------------------------+
58. enum EnumPriority {                         //Priority list on objects
59.             ePriorityNull = -1,
60.             ePriorityDefault = 0
61.                         };
62. //+------------------------------------------------------------------+

Defines.mqh

Observa que en la línea 55 definimos un nuevo evento. Ahora sí podemos pasar al procedimiento DispatchMessage de la clase C_ElementsTrade. Para explicar su funcionamiento con total claridad, lo analizaremos en un nuevo tema. Así será más fácil separar los conceptos y permitir que comprendas lo que está ocurriendo.


Procedimiento DispatchMessage. Qué confusión

Aunque el título de este tema sugiere que lo que haremos será confuso, en realidad te parecerá bastante sencillo si has comprendido los artículos anteriores y has estado estudiando el material y te has dedicado a aprender lo presentado en cada artículo. No tiene nada de confuso ni complicado. Antes de continuar, quiero mostrarte un detalle. Observa la siguiente imagen.

Observa que he resaltado una línea de la salida del compilador. ¿Qué tiene de especial para que merezca nuestra atención? Esa línea apareció debido a una advertencia del compilador. Para comprender su origen, veamos el fragmento de código que la provocó.

219. //+------------------------------------------------------------------+
220.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
221.         {
222.             string sz0;
223.             long _lparam = lparam;
224.             double _dparam = dparam;
225.             
226.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
227.             switch (id)
228.             {
229.                 case (CHARTEVENT_KEYDOWN):
230.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
231.                     _lparam = (long) m_Info.ticket;
232.                     _dparam = 0;
233.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
234.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
235.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
236.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
237.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
238.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
239.                     m_Info.bClick = false;
240.                 case CHARTEVENT_CHART_CHANGE:
241.                     UpdateViewPort(m_Info.price);
242.                     if (m_Info.ev != evMsgClosePositionEA)
243.                         ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
244.                     break;
245.                 case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
246.                     if (m_Info.ticket == (ulong)(_lparam))
247.                     {
248.                         m_Info.ViewMode = (stInfos::e1)((m_Info.ViewMode + 1) & 0x03);
249.                         EventChartCustom(0, evUpdate_Position, _lparam, 0, NULL);
250.                     }
251.                     break;
252.                 case CHARTEVENT_OBJECT_CLICK:
253.                     sz0 = GetPositionsMouse().szObjNameClick;
254.                     if (m_Info.bClick)
255.                     {
256.                         if (sz0 == def_NameBtnMove)
257.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
258.                         if (sz0 == def_NameBtnClose)
259.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL));
260.                         if (sz0 == def_NameObjLabel)
261.                             EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, 0, NULL);
262.                     }
263.                     m_Info.bClick = false;
264.                     break;
265.                 case CHARTEVENT_MOUSE_MOVE:
266.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
267.                     if (m_Info.weight > 1)
268.                     {
269.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
270.                         if (m_Info.ev != evMsgClosePositionEA)
271.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
272.                         if (m_Info.bClick)
273.                         {
274.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
275.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
276.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
277.                         }
278.                     }
279.                     break;
280.             }
281.         }
282. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa exactamente la línea indicada en la imagen anterior. En ella realizamos una conversión de tipo. El código del fragmento ya está actualizado para que no genere esa advertencia. Sin embargo, quiero que examines con atención la línea 248. Si has seguido el código cuidadosamente, notarás que allí utilizo algo que no aparece en el código. Por tanto, debemos corregir un detalle explicado en el tema anterior.

La cuestión es la siguiente: aunque las enumeraciones pueden ser anónimas, como hicimos en la línea 52 de la clase C_ElementsTrade, no podremos convertirlas correctamente si no asignamos un nombre al tipo de enumeración. Esto no se debe a ningún error del código ni del compilador. En MQL5 existe una función especial llamada EnumToString, cuyo propósito es convertir un valor numérico de una enumeración en un valor de tipo string. No se trata de una cadena cualquiera, sino del nombre exacto asociado a ese valor dentro de la enumeración.

Pero espera un momento. En este punto no estamos convirtiendo el valor en una cadena ni intentando obtener el nombre asociado a ese valor numérico dentro de la enumeración. Entonces, ¿por qué debemos prestar atención a la advertencia del compilador? ¿No podríamos ignorarla? La advertencia señala que la conversión del valor de la enumeración a string podría fallar si se intentara realizar. No es bueno que ignores todas las advertencias. Hay algunas que puedes ignorar y otras que debes corregir. Cada situación requiere su propio análisis; no existe una regla general aplicable a todos los casos.

En este caso, la conversión de tipo se realizará correctamente. No obstante, como no me gusta ver advertencias, prefiero corregir la causa que las genera. Para ello, solo tendremos que sustituir la línea 52 mostrada en el tema anterior por la que aparece a continuación.

052.   enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;

Una vez hecho esto, y manteniendo el código del procedimiento DispatchMessage tal como aparece en el fragmento anterior, el compilador dejará de generar la advertencia. Podemos volver entonces al procedimiento DispatchMessage de la clase C_ElementsTrade y entender cómo funciona. En sí no es complicado, aunque puede parecer confuso si no consigues seguir esta secuencia de artículos. Pero, si consigues entender las explicaciones de los artículos de esta serie sobre el sistema de repetición/simulador, este código es, en realidad, muy sencillo. Entonces, repasemos las novedades que incorpora.

En la línea 260 comprobamos si se ha hecho clic en el objeto OBJ_EDIT, que contiene el valor mostrado al operador o usuario de la plataforma. Si se cumple la condición, disparamos un evento en la línea 261. Ya expliqué cómo se interpreta este proceso. Después, en la línea 245, capturamos ese evento personalizado.

En la línea 246 comprobamos si el ticket coincide con el que utiliza el indicador de posición. En caso afirmativo, en la línea 248 alternamos cíclicamente entre los cuatro tipos de visualización. Después de cambiar el modo, en la línea 249 disparamos un nuevo evento cuyo objetivo es actualizar los demás segmentos del indicador de posición. Es decir, aunque hagas clic en el take profit, también se actualizarán el stop loss y el resultado de la operación. De este modo, evitamos que un segmento muestre un porcentaje mientras los demás presentan otro tipo de valor. El resultado puede verse en la siguiente animación.

Observa que, pese a haber añadido muy poco código al procedimiento DispatchMessage, hemos conseguido exactamente el comportamiento previsto y, en definitiva, un resultado satisfactorio. Sin embargo, todavía quiero explicar una situación que podría aplicarse a tu caso. Supongamos que deseas utilizar este indicador de posición en una cuenta de tipo HEDGING. Para quien no lo sepa, este tipo de cuenta es aquella en la que puedes mantener simultáneamente una posición de compra y otra de venta sobre el mismo símbolo. Aunque ya tengas una posición de compra, también puedes abrir otra nueva sin que una afecte directamente a la otra. Esta independencia es relativa, pues desde el punto de vista financiero ambas estarán correlacionadas de algún modo.

¿Qué sucede si tienes dos indicadores de posición en el gráfico? Básicamente, serán independientes entre sí. Al cambiar la forma de visualizar los datos de una posición, solo se modificarán los segmentos correspondientes a ella: el precio de apertura, el take profit y el stop loss. Los segmentos de las demás posiciones no sufrirán ningún cambio en la forma de mostrar el resultado de cada posición. Esto se debe a la comprobación realizada en la línea 246.

Si deseas que el cambio en la forma de visualizar los datos se aplique también a las demás posiciones, bastará con eliminar la comprobación de la línea 246. Sin embargo, antes de hacerlo debes tener en cuenta que existe un problema o, más concretamente, una falta de comunicación entre los indicadores de posición.

Para comprenderlo, primero debes saber que el indicador siempre se inicia en el modo cero de la enumeración declarada en la línea 52. Por tanto, cuando se añade al gráfico, comienza en el modo CERO. A continuación, utilizas el mecanismo mostrado en la animación anterior y cambias el modo de visualización, por ejemplo, al modo UNO. Recuerda que disponemos de CUATRO MODOS. Después abres una nueva posición en una cuenta de tipo HEDGING. En ese momento habrá un indicador en el modo UNO y otro en el modo CERO. Ahora piensa en lo siguiente: si eliminaste la comprobación de la línea 246, al cambiar el modo de visualización del indicador de la nueva posición al modo UNO también modificarás la visualización de la primera posición, que pasará del modo UNO al modo TRES. Ahí comienza la confusión. Si intentas devolver el modo de visualización de la primera posición al modo UNO, también cambiarás el modo de la nueva posición, esta vez al modo TRES.

En resumen, no es adecuado modificar el funcionamiento limitándose a eliminar una comprobación o cualquier otro elemento. Antes de hacerlo, debes detenerte a analizar las consecuencias. No pretendo desanimarte a modificar o adaptar el código a tus necesidades particulares. Solo quiero que comprendas lo que estás haciendo antes de intentar hacerlo.

Pero no quiero dejarte solo con este problema. Pensemos en una posible solución. Cuando se añade un indicador o un Asesor Experto al gráfico, primero se ejecuta el código de la función OnInit. MetaTrader 5 también dispara un evento que captura la función OnChartEvent: CHARTEVENT_CHART_CHANGE. Este dato es importante, porque nos permite sincronizar los indicadores de posición en cuanto se añade cada uno al gráfico.

No complicaré las cosas por ahora. Te mostraré solo una parte de la solución. Más adelante mostraré cómo crear una solución aún mejor, ya que acabará siendo necesaria. Veamos cómo puedes sincronizar los indicadores con el mínimo esfuerzo.

La primera parte de la solución aparece en el siguiente fragmento.

219. //+------------------------------------------------------------------+
220.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
221.         {
222.             string sz0;
223.             long _lparam = lparam;
224.             double _dparam = dparam;
225.             
226.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
227.             switch (id)
228.             {
229.                 case (CHARTEVENT_KEYDOWN):
230.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
231.                     _lparam = (long) m_Info.ticket;
232.                     _dparam = 0;
233.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
234.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
235.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
236.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
237.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
238.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
239.                     m_Info.bClick = false;
240.                 case CHARTEVENT_CHART_CHANGE:
241.                     UpdateViewPort(m_Info.price);
242.                     if (m_Info.ev != evMsgClosePositionEA)
243.                         ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
244.                     break;
245.                 case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
246.                     m_Info.ViewMode = (stInfos::e1)_dparam;
247.                     m_Info.ViewMode = (stInfos::e1)((m_Info.ViewMode + 1) & 0x03);
248.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, NULL);
249.                     break;
250.                 case CHARTEVENT_OBJECT_CLICK:
251.                     sz0 = GetPositionsMouse().szObjNameClick;
252.                     if (m_Info.bClick)
253.                     {
254.                         if (sz0 == def_NameBtnMove)
255.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
256.                         if (sz0 == def_NameBtnClose)
257.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL));
258.                         if (sz0 == def_NameObjLabel)
259.                             EventChartCustom(0, evMsgSwapViewModePosition, m_Info.ticket, (double)m_Info.ViewMode, NULL);
260.                     }
261.                     m_Info.bClick = false;
262.                     break;
263.                 case CHARTEVENT_MOUSE_MOVE:
264.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
265.                     if (m_Info.weight > 1)
266.                     {
267.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
268.                         if (m_Info.ev != evMsgClosePositionEA)
269.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
270.                         if (m_Info.bClick)
271.                         {
272.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
273.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
274.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
275.                         }
276.                     }
277.                     break;
278.             }
279.         }
280. //+------------------------------------------------------------------+

Fragmento de C_ElementTrade

Observa que ahora, en la línea 259, indicamos el valor actual de visualización mediante el parámetro dparam. Cuando se ejecute el código de la línea 246, asignaremos a todos los indicadores de posición el valor recibido a través de dparam. Es en este punto donde se produce la sincronización. Podría haber combinado las líneas 246 y 247 en una sola, pero preferí mantenerlas separadas para mostrarte con claridad cómo se realiza la sincronización. Si ejecutas el código y cambias la visualización, obtendrás el resultado que aparece en la siguiente animación.

Pregunta: ¿por qué los segmentos no aparecen sincronizados? La respuesta es que sí lo están, pero debemos actualizar el gráfico para que la sincronización se refleje visualmente. ¿A qué se debe esto? A la línea 248, que solo actualiza el indicador que recibió el evento de clic del usuario. ¿Cómo podemos resolver este inconveniente? Es sencillo: basta con actualizar todas las posiciones. Para ello, modificaremos el código como se muestra a continuación.

245.     case CHARTEVENT_CUSTOM + evMsgSwapViewModePosition:
246.         m_Info.ViewMode = (stInfos::e1)((((stInfos::e1)_dparam) + 1) & 0x03);
247.         for (int c0 = PositionsTotal() - 1; c0 >= 0; c0--)
248.             EventChartCustom(0, evUpdate_Position, PositionGetTicket(c0), 0, NULL);
249.         break;
250.     case CHARTEVENT_OBJECT_CLICK:

Fragmento de C_ElementsTrade

Observa que ahora me centro exclusivamente en la parte que realmente ha cambiado. Las dos líneas del código anterior se han combinado en una sola. Es decir, las líneas 246 y 247 del fragmento anterior están ahora contenidas en la línea 246, pues supongo que ya has comprendido su funcionamiento. La corrección propiamente dicha consiste en añadir la nueva línea 247. Fíjate en que, a diferencia del código anterior, ahora el evento evUpdate_Position se dispara para todas las posiciones abiertas. Con esto resolvemos el problema. El resultado puede verse en la siguiente animación.

Como ves, es bastante sencillo. Sin embargo, la solución todavía no es perfecta. Más adelante mostraré un código mejor, con un funcionamiento mucho más interesante. Por ahora, este enfoque será suficiente.


Consideraciones finales

En este artículo mostré cómo tú, entusiasta y sediento de conocimiento, puedes conseguir que el indicador de posición resulte aún más interesante de usar. Aun así, todavía no está completamente terminado y todavía requiere unas pocas modificaciones antes de que podamos utilizarlo realmente. La intención es disponer de un indicador que funcione tanto en el sistema de repetición/simulador como en el mercado, ya sea con una cuenta demo o con una cuenta real. El objetivo de esta serie es presentar todo el proceso de la forma más didáctica posible. Confío en que puedas seguir el desarrollo, aunque en algunos momentos parezca lento y aunque parezca lento y con pocas perspectivas de llegar a funcionar realmente en el sistema de repetición/simulador.

Pero estamos avanzando. Paso a paso. Para quienes solo deseen probar las aplicaciones desarrolladas hasta este punto, incluiré en los archivos adjuntos las aplicaciones ya compiladas. Solo tendrás que extraer los archivos y utilizarlos para comprobar el estado actual del proyecto. Recuerda que estas aplicaciones tienen una finalidad didáctica y pueden contener errores. Por tanto, utilízalas bajo tu propia responsabilidad.

ArchivoDescripción
Experts\Expert Advisor.mq5
Muestra la interacción entre Chart Trade y el Asesor Experto. Es necesario usar Mouse Study para la interacción.
Indicators\Chart Trade.mq5Crea la ventana para configurar la orden que se va a enviar. Es necesario usar Mouse Study para la interacción.
Indicators\Market Replay.mq5Crea los controles que permiten interactuar con el servicio de repetición/simulación de mercado. Es necesario usar Mouse Study para la interacción.
Indicators\Mouse Study.mq5Permite la interacción entre los controles gráficos y el usuario (necesario tanto para operar el sistema de repetición / simulador como en el mercado real).
Indicators\Order Indicator.mq5Se encarga de indicar las órdenes de mercado y permite interactuar con ellas y controlarlas.
Indicators\Position View.mq5Se encarga de indicar las posiciones de mercado y permite interactuar con ellas y controlarlas.
Services\Market Replay.mq5Crea y mantiene el servicio de repetición/simulación de mercado. Es el archivo principal de todo el sistema.

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13447

Archivos adjuntos |
Anexo.zip (779.24 KB)
Reimaginando las estrategias clásicas en MQL5 (Parte 13): Llevando nuestra estrategia de cruce a nuevas dimensiones (Parte 2) Reimaginando las estrategias clásicas en MQL5 (Parte 13): Llevando nuestra estrategia de cruce a nuevas dimensiones (Parte 2)
Únase a nosotros en nuestra discusión mientras buscamos mejoras adicionales para nuestra estrategia de cruce de medias móviles con el fin de reducir el retraso en nuestra estrategia de negociación a niveles más confiables, aprovechando nuestras habilidades en ciencia de datos. Es un hecho ampliamente estudiado que aumentar la dimensionalidad de los conjuntos de datos puede, en ocasiones, mejorar el rendimiento de los modelos de aprendizaje automático. Demostraremos qué significa esto en la práctica para usted como trader y le mostraremos cómo puede aprovechar este poderoso principio utilizando su terminal MetaTrader 5.
Del básico al intermedio: Colas, listas y árboles (VI) Del básico al intermedio: Colas, listas y árboles (VI)
En este artículo retomaremos la implementación de un árbol. Ahora que conocemos los conceptos básicos sobre el funcionamiento de los constructores y destructores, finalmente podremos corregir el código presentado en el último artículo. Prepárate para una verdadera aventura en la programación con MQL5.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Introducción a MQL5 (Parte 26): Creación de un EA basado en zonas de soporte y resistencia Introducción a MQL5 (Parte 26): Creación de un EA basado en zonas de soporte y resistencia
Este artículo te enseña cómo crear un Asesor Experto MQL5 que detecta automáticamente las zonas de soporte y resistencia y ejecuta operaciones basándose en ellas. Aprenderás a programar tu EA para identificar estos niveles clave del mercado, monitorear las reacciones de los precios y tomar decisiones de trading sin intervención manual.