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

Simulación de mercado: Position View (XVI)

MetaTrader 5Probador |
27 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos y bienvenidos a un nuevo artículo de la serie sobre cómo construir un sistema de repetición/simulación.

En el artículo anterior, Simulación de mercado: Position View (XV), finalmente llegamos a un punto en el que fue posible explicar con bastante claridad cómo y, sobre todo, por qué se está implementando de esa manera el sistema de repetición/simulador. Creo que muchos de ustedes no imaginaban que fuera posible hacer algo así con MQL5, ya que no he encontrado referencias anteriores que apliquen exactamente el enfoque explicado en el artículo anterior.

Sé que, a primera vista, este tipo de contenido puede intimidar un poco y, muchas veces, confundir o abrumar a los entusiastas. Sin embargo, estimado lector, mi intención no es que desistas ni que consideres inalcanzable lo que estoy explicando. Muy al contrario: quiero motivarte a estudiar y aprender nuevas técnicas de programación, a ampliar tu forma de ver las cosas y a salir de lo habitual.

En este artículo haremos algo más sencillo, pero necesario, para que puedas asimilar adecuadamente el contenido del artículo anterior. Aquí haremos que el indicador de posición resulte un poco más agradable.


Algunos pasos más

Muy bien. Como habrás observado, hasta ahora los controles del indicador de posición parecen estar algo dispersos. Aunque esto no dificulta su uso, podemos mejorar la presentación para el usuario final. Para ello, tendremos que crear algunos elementos, añadir información y realizar ciertos ajustes. Son cambios menores, pero harán que el indicador resulte más agradable de usar.

Añadir un objeto de fondo al indicador es una tarea muy sencilla y directa. A continuación puedes ver las modificaciones necesarias y los puntos del código donde se aplican. Ten en cuenta que se trata de un fragmento de código, por lo que necesitarás el código completo, disponible en los artículos anteriores.

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. //+------------------------------------------------------------------+

                                 .
                                 .
                                 .

049. //+------------------------------------------------------------------+
050.         void UpdateViewPort(const double price)
051.         {
052.             int x, y;
053.             
054.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
055.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
056.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
057.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
058.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
059.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
060.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
061.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
062.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
063.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
064.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
065.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
066.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
067.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
068.         }
069. //+------------------------------------------------------------------+
070. inline void CreateLinePrice(void)
071.         {
072.             string szObj;
073.             
074.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
075.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
076.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
077.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
078.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
079.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
080.             macro_LineInFocus(false);
081.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
082.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
083.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
084.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
085.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width + (m_Info.ev == evMsgClosePositionEA ? 40 : 32));
086.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height + 5);
087.         }
088. //+------------------------------------------------------------------+
089. inline void CreateBoxInfo(const bool bMove)
090.         {
091.             string szObj;
092.             const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0};
093.             
094.             CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
095.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
096.             ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c));
097.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon)));
098.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15));
099.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
100.         }
101. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que en la línea 9 añadimos una nueva definición: será el nombre del objeto que vamos a crear. Como no se ha modificado nada entre el bloque de definiciones y el procedimiento UpdateViewPort, no tiene sentido volver a reproducir aquí todo el código. Para simplificar, estoy mostrando únicamente los fragmentos o procedimientos que realmente han cambiado.

Observa que en las líneas 66 y 67 definimos la ubicación del objeto que servirá de fondo. Aquí aparece un detalle interesante. El cálculo de la línea 67 es el mismo que el de la línea 86, donde establecemos la altura del objeto que vamos a crear. Sin embargo, en la línea 67 dividimos el valor entre dos para colocar correctamente el objeto en la pantalla.

El valor 10 que se resta a la coordenada X en la línea 66 se debe a que la imagen utilizada en el botón de cierre mide 16 × 16 píxeles. Como la imagen está centrada, debemos desplazarla ocho píxeles hacia la izquierda. Sin embargo, si solo hiciéramos eso, el objeto de fondo comenzaría exactamente en el borde izquierdo de la imagen del botón. Para dejar un pequeño espacio, sumamos dos píxeles más, con lo que obtenemos el valor 10, utilizado en la línea 66 para posicionar el objeto con respecto al eje X. Es algo muy sencillo y directo.

Pasemos ahora a la línea 97. Observa que allí modifiqué la combinación de colores. Si la hubiéramos mantenido, no podríamos distinguir correctamente dónde se encuentra el objeto que se selecciona para moverlo. Más adelante mejoraremos este aspecto, pero por ahora necesitamos identificar con precisión su ubicación. Por eso intensifiqué su color, para que resulte más visible cuando el objeto de fondo se muestre en el indicador. Veamos ahora cómo se crea el objeto de fondo. Para ello, consulta el fragmento comprendido entre las líneas 81 y 86. Allí se crea el objeto y se definen sus propiedades. Como se trata de una operación relativamente habitual, me limito a mostrar dónde se crea y qué propiedades le asignamos. Creo que no tendrás dificultades para comprender este fragmento de código.

Con esto, tenemos todo relativamente listo. Digo «relativamente» porque, cuando abras una posición, el indicador de posición se creará y se colocará correctamente, mostrando sus elementos de la forma adecuada. Sin embargo, aún tenemos dos problemas. En realidad, si utilizas un equipo moderno, con un sistema operativo bien organizado y sin demasiadas aplicaciones MQL5 en ejecución dentro de MetaTrader 5, no tendrás ningún problema, sino únicamente una pequeña molestia. Esta aparece cuando haces clic en el botón para eliminar el take profit o el stop loss y, más tarde, decides volver a crear el take profit o el stop loss. En ese caso, el objeto de fondo oculta una parte del objeto de movimiento. Resolverlo es muy sencillo: basta con modificar el procedimiento UpdatePrice como se muestra a continuación.

149. //+------------------------------------------------------------------+
150. inline void UpdatePrice(const double open, const double price)
151.         {
152.             ObjectsDeleteAll(0, m_Info.szPrefixName);
153.             if (price > 0)
154.             {
155.                 CreateLinePrice();
156.                 CreateButtonClose();
157.             }
158.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
159.             if (price > 0)
160.                 CreateObjectInfoText();
161.             m_Info.open = open;
162.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
163.             if (m_Info.ev != evMsgClosePositionEA)
164.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
165.         }
166. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que solo hemos introducido un pequeño cambio en el código, pero con él resolvemos por completo el problema. Tal vez te preocupe que, al mover el ratón para modificar el valor del take profit o del stop loss, se produzca un número excesivo de llamadas o alguna situación similar, como eliminar los objetos en la línea 152 y volver a crearlos inmediatamente después. Si llegaste a esa conclusión con solo observar este código, te recomiendo que leas los artículos anteriores, porque todavía no has comprendido cómo funciona todo esto.

Muy bien, nuestro indicador de posición tiene un aspecto bastante atractivo. Sin embargo, todavía presenta algunos problemas. Por ejemplo, al desplazar las líneas de take profit o stop loss, estas chocan entre sí y, en determinados momentos, los objetos se superponen. Más adelante desarrollaremos una solución más adecuada. Por ahora, podemos aplicar una medida más sencilla, ya que en las cuentas de tipo NETTING solo habrá un indicador de posición en el gráfico. Aunque las cuentas de tipo HEDGING permiten tener más de un indicador de posición, de momento no nos preocuparemos por la presencia de otros indicadores de posición. Esto será solo por ahora.

La opción más sencilla consiste en desplazar correctamente los objetos a lo largo del eje X para evitar que se superpongan. En esta fase, solo necesitamos modificar los valores en un punto del código. El siguiente fragmento muestra dónde debes hacerlo.

049. //+------------------------------------------------------------------+
050.         void UpdateViewPort(const double price)
051.         {
052.             int x, y;
053.             
054.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
055.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 260 : 370));
056.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
057.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));

Fragmento de C_ElementsTrade

Compara este fragmento con los anteriores y podrás identificar la modificación. Como el cambio es bastante sencillo de comprender, no entraré en más detalles. Pasemos a la siguiente tarea.

Pensemos ahora en lo siguiente: muchos operadores prefieren visualizar el resultado financiero en lugar de un valor expresado en puntos. ¿Cómo podemos ofrecer ambas posibilidades modificando lo menos posible nuestro código? Recuerda que quiero que el indicador permita consultar tanto el resultado en puntos como su equivalente financiero. En una aplicación personal, probablemente utilizaría uno de esos valores como texto emergente. Es decir, al colocar el puntero del ratón sobre algún elemento del indicador, este mostraría el valor almacenado en la propiedad OBJPROP_TOOLTIP. Si asignas un texto a esta propiedad, aparecerá cuando el puntero se sitúe sobre el objeto.

Sin embargo, muchos operadores podrían considerar este mecanismo incómodo o poco práctico. Además, muchos podrían encontrar los datos difíciles de interpretar. Por eso, debemos mostrar el resultado financiero de la misma forma que ya mostramos los valores en puntos. Para calcularlo, necesitamos otros dos datos: el volumen de la posición abierta y el valor financiero de un punto. Con esta información podremos presentar el resultado financiero del mismo modo que el resultado en puntos. Así de sencillo y directo.


Mostrar el resultado financiero

Muy bien. En este punto hay algunas cosas que podemos hacer y otras que debemos hacer. Empecemos por el siguiente escenario. Ignoraremos por completo cualquier otro aspecto y haremos que los valores se expresen ahora en términos financieros. Para ello, lo primero será modificar el fragmento del código principal del indicador de posición que aparece a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property icon "/Images/Market Replay/Icons/Positions.ico"
004. #property description "Indicator for tracking an open position on the server."
005. #property description "This should preferably be used together with an Expert Advisor."
006. #property description "For more details see the same article."
007. #property version   "1.128"
008. #property link "https://www.mql5.com/pt/articles/13391"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. //+------------------------------------------------------------------+
012. #define def_ShortName "Position View"
013. //+------------------------------------------------------------------+
014. #include <Market Replay\Order System\C_ElementsTrade.mqh>
015. #include <Market Replay\Defines.mqh>
016. //+------------------------------------------------------------------+
017. input ulong user00 = 0;        //For Expert Advisor use
018. //+------------------------------------------------------------------+
019. struct st00
020. {
021.     ulong   ticket;
022.     string  szShortName,
023.             szSymbol;
024.     double  priceOpen,
025.             var;
026.     char    digits;
027.     bool    bIsBuy;
028. }m_Infos;
029. //+------------------------------------------------------------------+
030. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL;
031. //+------------------------------------------------------------------+
032. bool CheckCatch(ulong ticket)
033. {
034.     double vv, vs;
035.     
036.     ZeroMemory(m_Infos);
037.     m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket);
038.     if (!PositionSelectByTicket(m_Infos.ticket)) return false;
039.     if (ObjectFind(0, m_Infos.szShortName) >= 0)
040.     {
041.         m_Infos.ticket = 0;
042.         return false;
043.     }
044.     m_Infos.szSymbol = PositionGetString(POSITION_SYMBOL);
045.     m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
046.     vs = SymbolInfoDouble(PositionGetString(POSITION_SYMBOL), SYMBOL_TRADE_TICK_SIZE);
047.     vv = SymbolInfoDouble(PositionGetString(POSITION_SYMBOL), SYMBOL_TRADE_TICK_VALUE);
048.     m_Infos.var = vs / vv;
049.     IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
050.     EventChartCustom(0, evUpdate_Position, ticket, 0, "");
051.             
052.     return true;
053. }
054. //+------------------------------------------------------------------+
055. inline void ProfitNow(void)
056. {
057.     double ask, bid;
058.     
059.     ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK);
060.     bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID);    
061.     if (Open != NULL)
062.         (*Open).ViewValue((m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask));
063. }
064. //+------------------------------------------------------------------+
065. int OnInit()
066. {
067.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
068.     if (!CheckCatch(user00))
069.     {
070.         ChartIndicatorDelete(0, 0, def_ShortName);
071.         return INIT_FAILED;
072.     }
073. 
074.     return INIT_SUCCEEDED;
075. }
076. //+------------------------------------------------------------------+
077. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
078. {
079.     ProfitNow();
080.     
081.     return rates_total;
082. }
083. //+------------------------------------------------------------------+
084. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
085. {
086.     double volume;
087.     
088.     if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
089.     if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
090.     if (Stop != NULL)    (*Stop).DispatchMessage(id, lparam, dparam, sparam);
091.     switch (id)
092.     {
093.         case CHARTEVENT_CUSTOM + evUpdate_Position:
094.             if (lparam != m_Infos.ticket) break;
095.             if (!PositionSelectByTicket(m_Infos.ticket))
096.             {
097.                 ChartIndicatorDelete(0, 0, m_Infos.szShortName);
098.                 return;
099.             };
100.             if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, m_Infos.digits, StringFormat("%I64u : Position opening price.", m_Infos.ticket), m_Infos.bIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY));
101.             if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, m_Infos.digits, StringFormat("%I64u : Take Profit price.", m_Infos.ticket), m_Infos.bIsBuy);
102.             if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, m_Infos.digits, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), m_Infos.bIsBuy);
103.             volume = PositionGetDouble(POSITION_VOLUME);
104.             (*Open).UpdatePrice(0, m_Infos.priceOpen = PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var);
105.             (*Take).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_TP), volume, m_Infos.var);
106.             (*Stop).UpdatePrice(m_Infos.priceOpen, PositionGetDouble(POSITION_SL), volume, m_Infos.var);
107.             ProfitNow();
108.             break;
109.     }
110.     ChartRedraw();
111. };
112. //+------------------------------------------------------------------+
113. void OnDeinit(const int reason)
114. {
115.     delete Open;
116.     delete Take;
117.     delete Stop;
118. }
119. //+------------------------------------------------------------------+

Código Principal

Explicaré brevemente qué ha cambiado. Observa que en la línea 25 tenemos una nueva variable. Esta se inicializa en la línea 48 con los datos obtenidos en las líneas 46 y 47. Calculamos aquí este valor para evitar tener que recalcularlo continuamente. Como no cambiará mientras el indicador de posición permanezca en el gráfico, no tendremos que preocuparnos por volver a calcularlo. Este valor se utiliza junto con otro valor definido en la línea 103. Allí obtenemos el volumen de la posición abierta. Esto se debe a que el volumen de la posición solo cambiará cuando el Asesor Experto envíe un evento para actualizar los indicadores de posición. En algunos de esos casos, será necesario actualizar el volumen de la posición abierta.

Observa ahora las líneas 104 a 106, donde se utilizan estos dos nuevos valores. Por tanto, debemos centrar nuestra atención en el procedimiento UpdatePrice, que también tendremos que modificar. Una vez terminados los cambios en el código principal, podemos volver a la clase C_ElementsTrade.

Muy bien. Sabemos que necesitaremos espacio para almacenar dos nuevos valores de tipo double. No nos preocuparemos por el valor que nos proporcione el código principal, ya que siempre será un valor en puntos. La clase C_ElementsTrade se encargará de convertirlo y calcular correctamente el valor que debe mostrarse en términos financieros. Para ello, añadiremos dos variables nuevas a la estructura de la clase, como puede verse en el siguiente fragmento.

26. class C_ElementsTrade : private C_Mouse
27. {
28.     private    :
29. //+------------------------------------------------------------------+
30.         struct st00
31.         {
32.             struct st_01
33.             {
34.                 uchar   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.                         tickValue;
46.             bool        bClick,
47.                         bIsBuy;
48.             char        weight;
49.             color       _color;
50.         }m_Info;
51. //+------------------------------------------------------------------+
52.         void UpdateViewPort(const double price)

Fragmento de C_ElementsTrade

Las nuevas variables aparecen en las líneas 44 y 45. Como el constructor garantiza que se inicialicen en CERO, no necesitamos hacer nada más en este punto. Podemos pasar directamente al procedimiento UpdatePrice e inicializarlas allí, como muestra el siguiente fragmento.

151. //+------------------------------------------------------------------+
152. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
153.         {
154.             ObjectsDeleteAll(0, m_Info.szPrefixName);
155.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
156.             m_Info.tickValue = (var > 0 ? var : m_Info.tickValue);
157.             if (price > 0)
158.             {
159.                 CreateLinePrice();
160.                 CreateButtonClose();
161.                 CreateObjectInfoText();
162.             }
163.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
164.             m_Info.open = open;
165.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
166.             if (m_Info.ev != evMsgClosePositionEA)
167.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
168.         }
169. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Ahora presta atención. Los valores predeterminados de los dos últimos argumentos son cero. ¿Por qué? Porque este procedimiento se llama desde dos lugares. El primero es el código principal, desde donde recibiremos valores distintos de cero. El segundo es el procedimiento DispatchMessage de la clase C_ElementsTrade, desde donde esos valores serán cero. De este modo evitamos el riesgo de asignar un valor incorrecto. Aunque podría parecer tentador pasar como argumentos los valores que acabamos de declarar en la clase, prefiero definir directamente en la declaración del procedimiento el valor predeterminado CERO para esos argumentos.

Una vez hecho esto, observa las líneas 155 y 156, donde comprobamos si los valores de los argumentos son cero o positivos. Si son mayores que cero, inicializaremos o actualizaremos, según corresponda, las variables declaradas en las líneas 44 y 45.

Con esto, ya no necesitamos preocuparnos por conocer los volúmenes ni el valor del tick. La clase ya dispone de esos datos. Por tanto, la siguiente modificación debe realizarse en la función ViewValue, que será la encargada de mostrar el valor deseado. Su versión original aparece a continuación:

169. //+------------------------------------------------------------------+
170.         void ViewValue(const double profit)
171.         {
172.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("%." + (string)m_Info.Text.digits + "f", (profit < 0 ? -(profit) : profit)));
173.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
174.             ChartRedraw();
175.         }
176. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que en la línea 172 damos formato al valor que se mostrará al operador. Sin embargo, en este punto se está representando la cantidad de puntos o, para ser más precisos, la variación absoluta del precio, no su valor en puntos. Por tanto, solo necesitamos calcular el valor financiero a partir de la variación absoluta del precio y de los valores que ahora tenemos disponibles.

169. //+------------------------------------------------------------------+
170.         void ViewValue(const double profit)
171.         {
172.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume));
173.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
174.             ChartRedraw();
175.         }
176. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Muy bien. En principio, esto resuelve la necesidad de mostrar resultados financieros. ¿Por qué digo «en principio»? Porque ahora tenemos un problema de espacio. Los valores que se mostrarán pueden superar el ancho disponible en el objeto OBJ_EDIT. Cuando esto suceda, la información quedará truncada y los datos mostrados pueden resultar completamente confusos para el operador. No obstante, para realizar pruebas, ya podemos visualizar los resultados financieros en el indicador de posición; solo tenemos que compilar el código.

Muy bien. Ahora tenemos un nuevo problema que exige algo más de análisis. Para explicar con claridad qué debemos tener en cuenta, veamos el código completo de la clase C_ElementsTrade, que aparece a continuación con todas las modificaciones realizadas hasta este momento.

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.                 uchar   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.         }m_Info;
051. //+------------------------------------------------------------------+
052.         void UpdateViewPort(const double price)
053.         {
054.             int x, y;
055.             
056.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
057.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 260 : 370));
058.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
059.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
060.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
061.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
062.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
063.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
064.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
065.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
066.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
067.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
068.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
069.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
070.         }
071. //+------------------------------------------------------------------+
072. inline void CreateLinePrice(void)
073.         {
074.             string szObj;
075.             
076.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
077.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
078.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
079.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
080.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
081.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
082.             macro_LineInFocus(false);
083.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
084.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
085.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
086.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
087.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width + (m_Info.ev == evMsgClosePositionEA ? 40 : 32));
088.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height + 5);
089.         }
090. //+------------------------------------------------------------------+
091. inline void CreateBoxInfo(const bool bMove)
092.         {
093.             string szObj;
094.             const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0};
095.             
096.             CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
097.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
098.             ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c));
099.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? (m_Info.ev == evMsgCloseTakeProfit ? clrDarkGreen : clrMaroon) : (m_Info.bIsBuy ? clrDarkGreen : clrMaroon)));
100.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15));
101.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
102.         }
103. //+------------------------------------------------------------------+
104. inline void CreateObjectInfoText(void)
105.         {
106.             string szObj;
107.             
108.             CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault));
109.             ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName);
110.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize);
111.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack);
112.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color);
113.             ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER);
114.             ObjectSetInteger(0, szObj, OBJPROP_READONLY, true);
115.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height);
116.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width);
117.         }
118. //+------------------------------------------------------------------+
119. inline void CreateButtonClose(void)
120.         {
121.             string szObj;
122.             
123.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
124.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
125.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
126.         }
127. //+------------------------------------------------------------------+
128.     public    :
129. //+------------------------------------------------------------------+
130.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true)
131.             :C_Mouse(0, "")
132.         {
133.             uint w, h;
134.                 
135.             ZeroMemory(m_Info);
136.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
137.             m_Info._color = _color;
138.             m_Info.szDescr = szDescr;
139.             m_Info.bIsBuy = IsBuy;
140.             m_Info.Text.digits = digits;    
141.             TextSetFont(def_FontName, def_FontSize * -10);
142.             TextGetSize(StringFormat("%." + (string)digits + "f", 8888.88888), w, h);
143.             m_Info.Text.Width = (uchar) w + 4;
144.             m_Info.Text.Height = (uchar) h + 4;
145.         }
146. //+------------------------------------------------------------------+
147.         ~C_ElementsTrade()
148.         {
149.             ObjectsDeleteAll(0, m_Info.szPrefixName);
150.         }
151. //+------------------------------------------------------------------+
152. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
153.         {
154.             ObjectsDeleteAll(0, m_Info.szPrefixName);
155.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
156.             m_Info.var = (var > 0 ? var : m_Info.var);
157.             if (price > 0)
158.             {
159.                 CreateLinePrice();
160.                 CreateButtonClose();
161.                 CreateObjectInfoText();
162.             }
163.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
164.             m_Info.open = open;
165.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
166.             if (m_Info.ev != evMsgClosePositionEA)
167.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
168.         }
169. //+------------------------------------------------------------------+
170.         void ViewValue(const double profit)
171.         {
172.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume));
173.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
174.             ChartRedraw();
175.         }
176. //+------------------------------------------------------------------+
177.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
178.         {
179.             string sz0;
180.             long _lparam = lparam;
181.             double _dparam = dparam;
182.             
183.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
184.             switch (id)
185.             {
186.                 case (CHARTEVENT_KEYDOWN):
187.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
188.                     _lparam = (long) m_Info.ticket;
189.                     _dparam = 0;
190.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
191.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
192.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
193.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
194.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
195.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
196.                     m_Info.bClick = false;
197.                 case CHARTEVENT_CHART_CHANGE:
198.                     UpdateViewPort(m_Info.price);
199.                     if (m_Info.ev != evMsgClosePositionEA)
200.                         ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
201.                     break;
202.                 case CHARTEVENT_OBJECT_CLICK:
203.                     sz0 = GetPositionsMouse().szObjNameClick;
204.                     if (m_Info.bClick)
205.                     {
206.                         if (sz0 == def_NameBtnMove)
207.                             EventChartCustom(0, evMsgSetFocus, m_Info.ticket, m_Info.ev, "");
208.                         if (sz0 == def_NameBtnClose)
209.                             EventChartCustom(0, (ushort) m_Info.ev, m_Info.ticket, PositionGetDouble(m_Info.ev == evMsgCloseTakeProfit ? POSITION_SL : POSITION_TP), PositionGetString(POSITION_SYMBOL));
210.                     }
211.                     m_Info.bClick = false;
212.                     break;
213.                 case CHARTEVENT_MOUSE_MOVE:
214.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
215.                     if (m_Info.weight > 1)
216.                     {
217.                         UpdateViewPort(_dparam = GetPositionsMouse().Position.Price);
218.                         if (m_Info.ev != evMsgClosePositionEA)
219.                             ViewValue(m_Info.bIsBuy ? _dparam - m_Info.open : m_Info.open - _dparam);
220.                         if (m_Info.bClick)
221.                         {
222.                             if ((m_Info.ev == evMsgCloseTakeProfit) || (m_Info.ev == evMsgCloseStopLoss))
223.                                 EventChartCustom(0, (ushort)(m_Info.ev == evMsgCloseTakeProfit ? evMsgNewTakeProfit : evMsgNewStopLoss), m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
224.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
225.                         }
226.                     }
227.                     break;
228.             }
229.         }
230. //+------------------------------------------------------------------+
231. };
232. //+------------------------------------------------------------------+
233. #undef macro_LineInFocus
234. //+------------------------------------------------------------------+
235. #undef def_Btn_Close
236. #undef def_PathBtns
237. #undef def_FontName
238. #undef def_FontSize
239. //+------------------------------------------------------------------+
240. #undef def_NameBackGround
241. #undef def_NameObjLabel
242. #undef def_NameInfoDirect
243. #undef def_NameBtnMove
244. #undef def_NameBtnClose
245. #undef def_NameHLine
246. //+------------------------------------------------------------------+

C_ElementTrade

El problema se debe precisamente al ancho del objeto OBJ_EDIT. Este se ajusta en la línea 116, aunque el valor asignado a la propiedad OBJPROP_XSIZE se obtiene en la línea 142, dentro del constructor de la clase. Estos puntos no constituyen el problema. En realidad, forman parte de la solución. Sin embargo, en la línea 57 sí aparece un pequeño inconveniente. Aunque ajustemos el ancho del objeto OBJ_EDIT, los objetos del indicador podrían superponerse. Más adelante veremos cómo evitarlo, ya que será necesario realizar varios cambios. Hasta entonces, podemos convivir temporalmente con esa posibilidad. No obstante, para evitar que la superposición dificulte el uso del indicador, también tendremos que ajustar los valores de la línea 57.

Además, añadiremos la posibilidad de que el operador alterne entre los modos de visualización disponibles. Para ello, tendremos que modificar algunos elementos del código anterior. Para separar mejor esta explicación, pasemos a un nuevo tema.


Pequeños cambios para obtener mejores resultados

Lo más curioso de todo lo que haremos es que bastaría con modificar la línea 142 para aumentar el espacio disponible en el objeto OBJ_EDIT y resolver así todos los problemas con mucho menos esfuerzo. Sin embargo, eso no sería especialmente interesante. Quiero mostrarte cómo hacer que el ancho de OBJ_EDIT se ajuste dinámicamente a una medida más adecuada para mostrar los valores en el gráfico. Esta no es la parte complicada. La verdadera dificultad consiste en evitar que los objetos colisionen o se superpongan en el gráfico.

Antes de ocuparnos de ello, haremos lo siguiente: permitiremos que el procedimiento ViewValue indique al objeto OBJ_EDIT qué ancho debe utilizar. De este modo, obtendremos un ancho completamente dinámico. Esto generará de inmediato otro problema, pero primero implementaremos este cambio. Para ello, retiraremos algunas partes mencionadas en el apartado anterior y las trasladaremos al procedimiento ViewValue. Las modificaciones resultantes aparecen en el siguiente fragmento.

051. //+------------------------------------------------------------------+
052.         void UpdateViewPort(const double price)
053.         {
054.             int x, y;
055.             
056.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
057.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 260 : 370));
058.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
059.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
060.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
061.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
062.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
063.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
064.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
065.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
066.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + m_Info.Text.Width + 20);
067.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
068.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
069.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
070.         }
071. //+------------------------------------------------------------------+
072. inline void CreateLinePrice(void)
073.         {
074.             string szObj;
075.             
076.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
077.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
078.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
079.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
080.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
081.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
082.             macro_LineInFocus(false);
083.             CreateObjectGraphics(szObj = def_NameBackGround, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
084.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
085.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
086.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
087.         }
088. //+------------------------------------------------------------------+

                   .
                   .
                   .

101. //+------------------------------------------------------------------+
102. inline void CreateObjectInfoText(void)
103.         {
104.             string szObj;
105.             
106.             CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault));
107.             ObjectSetString(0, szObj, OBJPROP_FONT, def_FontName);
108.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, def_FontSize);
109.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack);
110.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color);
111.             ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER);
112.             ObjectSetInteger(0, szObj, OBJPROP_READONLY, true);
113.         }
114. //+------------------------------------------------------------------+

                   .
                   .
                   .
                                   
123. //+------------------------------------------------------------------+
124. inline void AdjustDinamic(const string szTxt)
125.         {
126.             uint w, h;
127. 
128.             TextSetFont(def_FontName, def_FontSize * -10);
129.             TextGetSize(szTxt, w, h);
130.             m_Info.Text.Height = (uchar) h + 4;
131.             m_Info.Text.Width = (uchar) w + 4;
132.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XSIZE, m_Info.Text.Width);
133.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YSIZE, m_Info.Text.Height);            
134.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + (m_Info.ev == evMsgClosePositionEA ? 40 : 32));
135.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5);
136.         }
137. //+------------------------------------------------------------------+
138.     public    :
139. //+------------------------------------------------------------------+
140.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true)
141.             :C_Mouse(0, "")
142.         {
143.             ZeroMemory(m_Info);
144.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
145.             m_Info._color = _color;
146.             m_Info.szDescr = szDescr;
147.             m_Info.bIsBuy = IsBuy;
148.             m_Info.Text.digits = digits;    
149.         }
150. //+------------------------------------------------------------------+
 
                   .
                   .
                   .
                                   
173. //+------------------------------------------------------------------+
174.         void ViewValue(const double profit)
175.         {
176.             string szTxt;
177.             
178.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
179.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
180.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
181.             AdjustDinamic(szTxt);
182.             ChartRedraw();
183.         }
184. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Puedes comparar este fragmento con el código completo del apartado anterior y localizar fácilmente los cambios. Cabe señalar que en la línea 124 aparece un nuevo procedimiento cuyo objetivo es hacer que el diseño sea completamente dinámico, de modo que tanto OBJ_EDIT como OBJ_BITMAP_LABEL puedan adaptarse al ancho del texto que se mostrará.

Observa también que hemos modificado ligeramente el procedimiento ViewValue para ajustar de forma dinámica el ancho de OBJ_EDIT. No obstante, podemos mejorar este enfoque, ya que en la línea 181 actualizamos ese ancho en cada llamada, incluso cuando el ancho del texto no cambia. Para evitar que la línea 181 llame continuamente al procedimiento AdjustDinamic, basta con almacenar el ancho del texto que se mostrará. Si el ancho no cambia, no tiene sentido volver a llamar a AdjustDinamic.

Tal vez estés pensando que la solución es sencilla: utilizar una variable estática para almacenar ese ancho. Si no has entendido la idea, el código de ViewValue quedaría como se muestra a continuación:

173. //+------------------------------------------------------------------+
174.         void ViewValue(const double profit)
175.         {
176.             static int sizeTxt = 0;
177.             string szTxt;
178.             
179.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
180.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
181.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
182.             if (StringLen(szTxt) != sizeTxt)
183.             {
184.                 AdjustDinamic(szTxt);
185.                 sizeTxt = StringLen(szTxt);
186.             }
187.             ChartRedraw();
188.         }
189. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Este razonamiento no es incorrecto y, en muchos casos, la solución funcionaría. Sin embargo, precisamente en este código, la solución no funcionará. ¿Por qué? Porque la clase C_ElementsTrade se comparte entre tres segmentos del código: uno para mostrar el take profit, otro para el stop loss y otro para el precio de apertura. Si utilizas una variable estática en el procedimiento ViewValue, el compilador asignará una única posición de memoria para esa variable y los tres segmentos accederán a esa misma posición. Presta atención a este detalle, porque es muy habitual intentar aplicar esta solución y obtener resultados distintos de los previstos durante la fase de planificación. Las variables estáticas deben utilizarse con sumo cuidado, al igual que las variables globales, que pueden ser visibles en todo el código. Dentro de poco utilizaremos esta misma construcción de una forma más adecuada.

Si no has comprendido lo que acabo de explicar, estimado lector y entusiasta, vuelve a leer este fragmento hasta asimilarlo, pues este concepto es importante, especialmente cuando queremos aplicar determinados métodos de programación. Como hemos visto, la variable estática no nos sirve en este caso. Sin embargo, la solución que debemos implementar es muy similar a la mostrada en el fragmento anterior. Veamos ahora cómo debe implementarse. Esto se muestra en el siguiente fragmento.

025. //+------------------------------------------------------------------+
026. class C_ElementsTrade : private C_Mouse
027. {
028.     private    :
029. //+------------------------------------------------------------------+
030.         struct st00
031.         {
032.             struct st_01
033.             {
034.                 uchar   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. //+------------------------------------------------------------------+

                   .
                   .
                   .
                                   
156. //+------------------------------------------------------------------+
157. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0)
158.         {
159.             m_Info.sizeText = 0;
160.             ObjectsDeleteAll(0, m_Info.szPrefixName);
161.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
162.             m_Info.var = (var > 0 ? var : m_Info.var);
163.             if (price > 0)
164.             {
165.                 CreateLinePrice();
166.                 CreateButtonClose();
167.                 CreateObjectInfoText();
168.             }
169.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
170.             m_Info.open = open;
171.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
172.             if (m_Info.ev != evMsgClosePositionEA)
173.                 ViewValue(m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
174.         }
175. //+------------------------------------------------------------------+
176.         void ViewValue(const double profit)
177.         {
178.             string szTxt;
179.             
180.             szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", ((profit < 0 ? -(profit) : profit) / m_Info.var) * m_Info.volume);
181.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
182.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
183.             if (StringLen(szTxt) != m_Info.sizeText)
184.             {
185.                 AdjustDinamic(szTxt);
186.                 m_Info.sizeText = StringLen(szTxt);
187.             }
188.             ChartRedraw();
189.         }
190. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que en la línea 50 añadimos una nueva variable. Esta desempeñará la misma función que la variable estática, pero cada uno de los tres segmentos tendrá una posición de memoria propia para esta variable. Por tanto, cada segmento podrá conservar un valor distinto. En este fragmento también he incluido el procedimiento UpdatePrice para mostrar dónde se restablece la variable declarada en la línea 50. Esto sucede en la línea 159. ¿Por qué? Porque, al desplazar las líneas de take profit o stop loss, el ancho del objeto OBJ_EDIT puede cambiar radicalmente. Para garantizar que siempre se calcule un ancho adecuado, restablecemos aquí el valor de la variable.

El valor se asigna de la misma forma que en el fragmento anterior, como puedes comprobar en la línea 186. Con esto resolvemos uno de los problemas. Ahora debemos ocuparnos de los restantes. Como ya hemos abordado numerosos conceptos en este artículo y no quiero complicar innecesariamente la explicación, terminaremos viendo cómo ajustar dinámicamente la posición en el eje X entre los distintos segmentos utilizados en el indicador de posición.

El código necesario aparece en el siguiente fragmento:

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. //+------------------------------------------------------------------+

                                   .
                                   .
                                   .
                                   
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. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

A primera vista, este fragmento puede parecer bastante disparatado y carente de lógica, e incluso dar la impresión de que ocurre algo casi mágico. Pero no. Lo que estamos haciendo es utilizar las variables estáticas de una forma práctica y adecuada. Observa que combinamos variables estáticas, cada una con una única posición de memoria compartida por todos los segmentos, con variables convencionales, cada una con una posición de memoria distinta en cada segmento. Pero ¿por qué hacer esta locura que aparece en el fragmento? La razón es la simplicidad, por extraño que parezca. Resulta más sencillo declarar en las líneas 55 y 56 dos variables estáticas que todos los segmentos de la clase C_ElementsTrade podrán leer que crear una variable global o una variable estática en el código principal.

También podrías hacerlo así. Sin embargo, al declarar las variables como estáticas dentro de una función que pertenece a la clase C_ElementsTrade, ocultamos y encapsulamos de forma muy eficaz los detalles internos de la implementación. El código principal que utilizará esta clase no necesita saber cómo funciona la clase ni si necesita algún tipo especial de variable. Al utilizar la clase C_ElementsTrade, podrías olvidar o no comprender la función de una variable global declarada en el código principal. Por eso es preferible encapsular todo el código de esta forma.

A diferencia de lo que ocurría antes, cuando se producía un efecto secundario no deseado, en la función UpdateViewPort sí queremos que cada una de las variables _SizeControl y _Width tenga una única posición de memoria compartida por todos los segmentos. Al hacer esto, obtendremos exactamente el resultado que muestra la siguiente animación.

Observa que, al desplazar la línea de take profit hasta una posición que obliga a modificar el ancho del objeto OBJ_EDIT, cuando termina el movimiento también se actualizan los demás objetos OBJ_EDIT del indicador de posición. Sé que puede parecer algo mágico o incluso imposible, ya que probablemente no esperabas que se produjera este comportamiento.


Consideraciones finales

En este artículo realizamos las modificaciones necesarias para que el indicador de posición pudiera mostrar un resultado financiero. De este modo, el operador podría hacerse una idea del valor financiero que obtendría con una posición abierta. Aunque algunos operadores prefieran consultar otro tipo de información, considero que el artículo ha cumplido su objetivo y te ha presentado un concepto que muchos desconocen, incluso después de utilizar MQL5 durante mucho tiempo: cómo emplear variables estáticas para que varios segmentos accedan a la misma posición de memoria asignada a una variable sin tener que declarar una variable global en el código principal. Esta última opción puede provocar efectos secundarios especialmente inconvenientes en el código de una clase, ya que las variables globales tienden a romper el encapsulamiento de la clase.

Sé que el contenido de este artículo puede resultar bastante confuso al principio. Aun así, te recomiendo estudiar con atención tanto lo explicado aquí como lo mostrado en estos artículos de la serie sobre la construcción de un sistema de repetición/simulador. Para quienes deseen probar las aplicaciones y comprobar si merece la pena esforzarse en estudiar este material, incluiré en los archivos adjuntos los códigos ya compilados.

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 para interactuar con el servicio de repetición y 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 y simulación de mercado (Archivo principal de todo el sistema)

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

Archivos adjuntos |
Anexo.zip (779.24 KB)
Del básico al intermedio: Clases (III) Del básico al intermedio: Clases (III)
En este artículo veremos cómo controlar mejor el código cuando trabajamos con programación orientada a objetos. Aunque apenas estamos comenzando a estudiar la programación orientada a objetos, lo que veremos aquí te ayudará a comprender diversos aspectos. También reducirá las dudas que puedan surgir más adelante.
Del básico al intermedio: Clases (II) Del básico al intermedio: Clases (II)
Este artículo busca ser lo más didáctico posible, porque el tema que abordaremos ya suele generar bastante confusión. Por eso, mi querido lector, intenta poner en práctica lo que se explica aquí. Ante cualquier duda, no dejes de comentar, pues comprender los destructores no es precisamente una de las tareas más sencillas.
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.
Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR) Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR)
Descubra cómo los modelos de autorregresión vectorial (VAR) pueden pronosticar datos de series temporales OHLC (apertura, máximo, mínimo y cierre) del mercado Forex. Este artículo trata sobre la implementación de VAR, el entrenamiento de modelos y la previsión en tiempo real en MetaTrader 5, lo que ayuda a los traders a analizar los movimientos interdependientes de las divisas y a mejorar sus estrategias de trading.