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

Simulación de mercado: Position View (XX)

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 (XIX), añadimos un mecanismo para comprobar si estamos ajustando correctamente el stop loss o el take profit. De este modo, ambos niveles pueden configurarse para que el precio actual del símbolo permanezca entre ellos. Probablemente habrás observado que lo hicimos de una forma bastante sencilla y práctica. He intentado mantener ese enfoque a lo largo de la serie: introducir cambios simples, aprovechar al máximo el código ya implementado y presentar cada paso de la forma más didáctica posible. El objetivo es que puedas seguir el desarrollo y, sobre todo, modificar el código para implementar una solución que se adapte mejor a tus necesidades.

En este artículo seguiremos el mismo enfoque. Hace algún tiempo comenzamos a desarrollar el mecanismo que ahora examinaremos e implementaremos con mayor profundidad. Quizá esta función no te parezca especialmente necesaria. Sin embargo, te aseguro que será muy interesante por la forma en que realizaremos la implementación. Introduciremos una pequeña modificación para mostrar al operador o usuario dónde se encontraba anteriormente el nivel de stop loss o take profit, antes de que comiences a desplazarlo hacia una nueva ubicación.

Para que comprendas mejor lo que vamos a implementar, quiero que tengas presente lo siguiente: cuando seleccionamos uno de los segmentos —el take profit o el stop loss— para cambiar su ubicación, el valor no se actualiza mientras desplazamos la línea. Primero debemos finalizar el movimiento, ya sea haciendo clic o pulsando ESC. Solo entonces el indicador de posición solicita al Asesor Experto que envíe una petición al servidor de trading.

El objetivo de esa petición es modificar el valor original del take profit o del stop loss. Sin embargo, mientras desplazamos la línea, el servidor sigue conservando el valor anterior. En este artículo implementaremos precisamente una forma de mostrar ese valor anterior. Crearemos una forma de mostrar dónde continúa situado el precio registrado en el servidor. Así, si la posición se cierra antes de que termines de desplazar la línea hacia el nuevo nivel, sabremos por qué ocurrió y el comportamiento del indicador de posición no parecerá extraño.

Para crear —o, mejor dicho, modificar— el indicador de posición con el fin de implementar este comportamiento, primero debes comprender algunos detalles. Pasemos al primer apartado del artículo para comenzar a introducir los cambios necesarios.


Comprender cómo crear una sombra del precio

Como mencioné en la introducción, crearemos una especie de sombra cuyo objetivo será mostrar dónde se encontraba, o dónde continúa encontrándose, el precio antes de que finalice el desplazamiento que estamos realizando. Desde el punto de vista de la implementación, crear esta sombra resulta bastante interesante, ya que no haremos las cosas de una manera completamente disparatada. Simplemente indicaremos a MetaTrader 5 cómo debe interpretar y gestionar los objetos del indicador de posición.

En este punto del desarrollo quizá pienses que añadir una nueva funcionalidad al indicador nos obligará a escribir una gran cantidad de código o, como mínimo, a modificar profundamente el código existente. Sin embargo, no será necesario. Como el código se ha diseñado para ser lo más modular posible, tendremos que cambiar muy pocas cosas. Quizá sea necesario añadir alguna rutina nueva, pero siempre debemos intentar utilizar primero lo que ya está implementado. De este modo reducimos al mínimo la cantidad de pruebas necesarias para garantizar que todo funciona correctamente y agilizamos al máximo el desarrollo de cualquier aplicación.

Partiendo de la idea de modificar la menor cantidad posible de código, veamos cómo crear esta sombra. En primer lugar, debemos definir un aspecto importante: los colores. No queremos generar confusión al observar el gráfico. Por tanto, definiremos los nuevos colores y los nuevos nombres como se muestra a continuación. Esto nos permitirá distinguir los objetos que forman parte de la sombra de aquellos que no pertenecen a ella. Para ello, solo debemos añadir al código lo que aparece en el siguiente fragmento.

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. #define def_NameVolume     m_Info.szPrefixName + "#VOLUME"
011. //+------------------------------------------------------------------+
012. #define def_GhostColor     clrDarkGray
013. #define macro_GhostName(A) (m_Info.szPrefixName + "[GHOST]" + (A != "" ? StringSubstr(A, StringFind(A, "#")) : ""))
014. //+------------------------------------------------------------------+
015. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
016. //+------------------------------------------------------------------+
                                   .
                                   .
                                   .
332. //+------------------------------------------------------------------+
333. #undef def_GhostColor
334. #undef macro_GhostName
335. //+------------------------------------------------------------------+
336. #undef macro_LineInFocus
337. //+------------------------------------------------------------------+
338. #undef def_Btn_Close
339. #undef def_PathBtns
340. #undef def_FontName
341. #undef def_FontSize
342. //+------------------------------------------------------------------+
343. #undef def_NameVolume
344. #undef def_NameBackGround
345. #undef def_NameObjLabel
346. #undef def_NameInfoDirect
347. #undef def_NameBtnMove
348. #undef def_NameBtnClose
349. #undef def_NameHLine
350. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa cómo empieza a tomar forma la implementación. En la línea 12 definimos el color que utilizaremos. En la línea 13 declaramos una macro cuyo objetivo es permitirnos cambiar los nombres de los objetos. Sin embargo, esta macro no funcionará por sí sola. Es únicamente una pequeña parte de una implementación más amplia. Avancemos con calma. Primero, fíjate en que las líneas 333 y 334 ya contienen el código encargado de eliminar las definiciones creadas en las líneas 12 y 13. Antes de continuar, veamos qué ocurre en la macro de la línea 13. Para comprenderlo, observa los nombres de los objetos entre las líneas 4 y 10. Todos comienzan con un valor predefinido y comparten un mismo elemento: el carácter #. Ese es precisamente el punto que nos interesa.

En la macro seguiremos utilizando el valor predefinido para facilitar la eliminación de los objetos creados por el indicador de posición. Entre el prefijo y el resto del nombre original definido entre las líneas 4 y 10 añadiremos el texto [GHOST], manteniendo intacto el resto del nombre. Esta es precisamente la función de las llamadas StringSubstr y StringFind: eliminar del nombre la parte correspondiente a m_info.szPrefixName y conservar el resto del nombre original. Por ejemplo, al utilizar la macro para cambiar el nombre del objeto de fondo, un nombre como: 1685452684#BACKGROUND pasará a ser: 1685452684[GHOST]#BACKGROUND Todo esto se consigue únicamente mediante la macro. Sin embargo, como ya indiqué, la macro no funcionará por sí sola. Necesitamos código adicional que respalde este mecanismo. Si cambiamos los nombres de los objetos sin garantizar que podamos seguir accediendo a ellos, corremos el riesgo de desorganizar por completo el funcionamiento del indicador.

Para que comprendas cómo se realizará este proceso, examinaremos un código muy sencillo que muestra cómo funciona el cambio de nombre. El código completo aparece a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. #define prefixName "Obj #"
08. #define macro_Swap(A) (prefixName + "[GHOST]" + StringSubstr(A, StringFind(A, "#") + 1))
09. #define def_ObjectName(A) (prefixName + (string)(A))
10. //+------------------------------------------------------------------+
11. void ChartOfTest(string sz1, int x, int y, int zOrder)
12. {
13.     long id = ChartID();
14.     
15.     ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0);
16.     ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x);
17.     ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y);
18.     ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder);
19.     ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true);
20. }
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.     ChartOfTest(def_ObjectName(1), 160, 140, 1);
25.     
26.     return INIT_SUCCEEDED;
27. }
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
30. {
31.     return rates_total;
32. }
33. //+------------------------------------------------------------------+
34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
35. {
36.     switch (id)
37.     {
38.         case CHARTEVENT_OBJECT_CLICK:
39.             if (StringSubstr(sparam, 0, StringFind(sparam, "#") + 1) == prefixName)
40.                 ObjectSetString(0, def_ObjectName(1), OBJPROP_NAME, macro_Swap(def_ObjectName(1)));
41.             break;
42.     }
43. }
44. //+------------------------------------------------------------------+
45. void OnDeinit(const int reason)
46. {
47.     ObjectsDeleteAll(ChartID(), prefixName);
48. }
49. //+------------------------------------------------------------------+

Código de Demonstração

Sé que puede parecerte algo confuso, pero en realidad es bastante sencillo. Lo único que hacemos es probar y comprender lo que haremos en el indicador de posición. La idea consiste en crear un objeto que, en este caso, será de tipo OBJ_CHART. El objeto se crea en la línea 15 y su nombre se establece en la línea 24. Para definir ese nombre utilizamos la definición de la línea 9, que incluye un prefijo definido en la línea 7. El uso de este prefijo nos permite eliminar fácilmente el objeto OBJ_CHART creado por el código, como puede verse en la línea 47. Ahora presta atención. Cuando añadas esta aplicación a un gráfico y pulses la combinación CTRL + B, se te mostrará una ventana para que veas todos los objetos presentes en el gráfico. Puedes verla en la siguiente imagen.

Si el objeto OBJ_CHART no aparece en la lista, basta con hacer clic en el botón LIST ALL, como se muestra en la imagen. Observa entonces el nombre que presenta MetaTrader 5. A continuación, cierra la ventana que enumera los objetos y haz clic en el objeto OBJ_CHART. Esto hará que MetaTrader 5 dispare un evento, que será capturado por la línea 38 del código de prueba. En la línea 39 comprobamos si el nombre del objeto sobre el que se hizo clic comienza con el prefijo definido en la línea 7. Si se cumple esta condición, en la línea 40 cambiaremos el nombre del objeto OBJ_CHART. ¿Pero qué nombre le asignaremos?

El nuevo nombre se genera mediante la macro. Debemos proporcionarle el nombre original del objeto y, a partir de él, la macro de la línea 8 crea un nuevo nombre para el objeto. Observa que el código de esta macro es muy similar al que estamos incorporando al archivo de cabecera C_ElementsTrade.mqh. Una vez asignado el nuevo nombre, volvemos a pulsar CTRL + B y comprobamos que el objeto aparece ahora con otro nombre, como se muestra en la siguiente imagen.

Si nunca lo has hecho, pruébalo para observar el resultado. En determinados casos puede ser una técnica bastante interesante. Este es precisamente el mecanismo que utilizaremos en el indicador de posición para crear la sombra. Ahora que ya contamos con los fundamentos y los conocimientos necesarios, podemos continuar. Para separar mejor las distintas partes, pasemos a un nuevo apartado.


Crear la sombra del precio

Ahora que sabes que es posible cambiar el nombre de los objetos —en caso de que aún no conocieras esta posibilidad—, ha llegado el momento de aplicar ese conocimiento para crear la sombra que necesitamos. Antes, veamos cómo lo haremos. Cuando creamos nuevos objetos, estos quedan en la parte superior de la lista de objetos. Como ya vimos en artículos anteriores, los nuevos objetos se superponen a los antiguos, que pasan a ser objetos de fondo. Por tanto, la forma más sencilla de crear la sombra consiste en mantener los objetos anteriores en el gráfico, cambiarles el nombre y, acto seguido, crear los nuevos. Así de sencillo.

Sin embargo, el código actual no nos permite seguir mostrando los objetos después de cambiarles el nombre. Antes de analizar las modificaciones, quiero aclarar que, en esta primera fase, no nos preocupa que exista código duplicado. Lo importante es conseguir que funcione como esperamos: debemos poder crear la sombra del precio y, al mismo tiempo, mantener el comportamiento anterior. Por tanto, primero debemos resolver cómo mostrar la sombra para poder avanzar con la implementación. Para ello, basta con modificar el código como se muestra en el siguiente fragmento.

029. //+------------------------------------------------------------------+
030. class C_ElementsTrade : private C_Mouse
031. {
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stInfos
035.         {
036.             struct st_01
037.             {
038.                 short   Width,
039.                         Height,
040.                         digits;
041.             }Text;
042.             ulong       ticket;
043.             string      szPrefixName,
044.                         szDescr,
045.                         szSymbol,
046.                         szMsgGhost;
047.             EnumEvents  ev;
048.             double      price,
049.                         open,
050.                         volume,
051.                         var,
052.                         tickSize,
053.                         tpsl,
054.                         limit,
055.                         priceGhost;
056.             bool        bClick,
057.                         bIsBuy;
058.             char        weight;
059.             color       _color;
060.             int         sizeText;
061.             enum e1 {eValue, eFinance, eTicks, ePercentage} ViewMode;
062.         }m_Info;
063. //+------------------------------------------------------------------+
064.         short UpdateViewPort(const double price, short size = 0, uint ui = 0)
065.         {
066.             static short _SizeControls;
067.             static short _Width;
068.             uint x, y;
069.             
070.             if (size > 0)
071.             {
072.                 size += (short)(ui + 8);
073.                 _SizeControls = (_SizeControls > size ? _SizeControls : size);
074.                 size = (short)(_SizeControls - ui - 12);
075.                 _Width = (_Width > size ? _Width : size);
076.             }else
077.             {
078.                 ChartTimePriceToXY(0, 0, 0, price, x, y);            
079.                 x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2)));
080.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
081.                 ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
082.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
083.                 ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
084.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0));
085.                 ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
086.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + (_Width * 2) + 20);
087.                 ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
088.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + _Width + 20);
089.                 ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
090.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_XDISTANCE, x - 10);
091.                 ObjectSetInteger(0, def_NameBackGround, OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
092.                 ObjectSetInteger(0, def_NameVolume, OBJPROP_XDISTANCE, x + 10);
093.                 ObjectSetInteger(0, def_NameVolume, OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
094.                 if (m_Info.szMsgGhost != "")
095.                 {
096.                     ChartTimePriceToXY(0, 0, 0, m_Info.priceGhost, x, y);
097.                     x = 125 + (m_Info.ev == evMsgClosePositionEA ? 0 : _Width + (m_Info.ev == evMsgCloseTakeProfit ? _SizeControls : (_SizeControls * 2)));
098.                     ObjectSetInteger(0, macro_GhostName(def_NameHLine), OBJPROP_XDISTANCE, x);
099.                     ObjectSetInteger(0, macro_GhostName(def_NameHLine), OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
100.                     ObjectSetInteger(0, macro_GhostName(def_NameObjLabel), OBJPROP_XDISTANCE, x + 10 + (m_Info.ev == evMsgClosePositionEA ? _Width + 2 : 0));
101.                     ObjectSetInteger(0, macro_GhostName(def_NameObjLabel), OBJPROP_YDISTANCE, y - (m_Info.Text.Height / 2));
102.                     ObjectSetInteger(0, macro_GhostName(def_NameBtnMove), OBJPROP_XDISTANCE, x + _Width + 20);
103.                     ObjectSetInteger(0, macro_GhostName(def_NameBtnMove), OBJPROP_YDISTANCE, y);
104.                     ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_XDISTANCE, x - 10);
105.                     ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_YDISTANCE, y - ((m_Info.Text.Height + 5) / 2));
106.                 }
107.             }
108.             return _Width;
109.         }
110. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Aquí puedes ver algunos cambios. En primer lugar, en las líneas 46 y 55 declaramos dos nuevas variables. Ambas se inicializan en el constructor, igual que las demás. Sin embargo, debido a la forma en que el constructor inicializa la estructura stInfos, las dos reciben sus valores predeterminados. La variable szMsgGhost comienza vacía, mientras que priceGhost se inicializa con el valor cero. Por tanto, no necesitamos realizar ninguna inicialización adicional. Presta atención a la prueba de la línea 94, que evita que intentemos dibujar algo que todavía no se encuentra en el gráfico. Como inicialmente m_Info.szMsgGhost está vacío, la prueba fallará. Quizá te preguntes por qué no comprobamos m_Info.priceGhost, lo que aparentemente sería mucho más sencillo. En efecto, esa comprobación sería más simple, pero más adelante verás que generaría algunos inconvenientes. Por ese motivo utilizamos m_Info.szMsgGhost.

Observa que el código comprendido entre las líneas 96 y 105 se parece mucho al que posiciona los objetos que no forman parte de la sombra. Sin embargo, aunque ambos fragmentos son muy similares, este contiene algunas particularidades que puedes notar fácilmente.

Muy bien. Ya podemos mostrar en el gráfico los objetos que actuarán como sombras de los originales e indicarán dónde se encontraba el precio. El siguiente cambio aparece en el fragmento que se muestra a continuación.

160. //+------------------------------------------------------------------+
161. inline void AdjustDinamic(const string szObj, const string szTxt)
162.         {
163.             uint w, h;
164. 
165.             TextSetFont(def_FontName, def_FontSize * -10);
166.             TextGetSize(szTxt, w, h);
167.             m_Info.Text.Height = (uchar) h + 4;
168.             m_Info.Text.Width = (uchar) w + 4;
169.             m_Info.Text.Width = UpdateViewPort(0, m_Info.Text.Width, h = 32);
170.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, m_Info.Text.Width);
171.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, m_Info.Text.Height);
172.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0));
173.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_YSIZE, m_Info.Text.Height + 5);
174.             if (m_Info.szMsgGhost != "")
175.             {
176.                 ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_XSIZE, m_Info.Text.Width + h + (m_Info.ev == evMsgClosePositionEA ? m_Info.Text.Width + 8 : 0));
177.                 ObjectSetInteger(0, macro_GhostName(def_NameBackGround), OBJPROP_YSIZE, m_Info.Text.Height + 5);
178.                 ObjectSetString(0, macro_GhostName(def_NameObjLabel), OBJPROP_TEXT, m_Info.szMsgGhost);
179.             }
180.         }
181. //+------------------------------------------------------------------+
182. inline void ChartChange(void)
183.         {
184.             UpdateViewPort(MathAbs(m_Info.price));
185.             m_Info.limit = (m_Info.bIsBuy ? m_Info.price - m_Info.open : m_Info.open - m_Info.price);
186.             if (m_Info.ev != evMsgClosePositionEA)
187.                 ViewValue(m_Info.limit);
188.         }
189. //+------------------------------------------------------------------+
190.         void CreateGhost(void)
191.         {
192.             ObjectSetInteger(0, def_NameHLine, OBJPROP_BGCOLOR, def_GhostColor);
193.             ObjectSetInteger(0, def_NameHLine, OBJPROP_COLOR, def_GhostColor);
194.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, def_GhostColor);
195.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BORDER_COLOR, def_GhostColor);
196.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_COLOR, def_GhostColor);
197.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_BGCOLOR, def_GhostColor);
198.             ObjectSetInteger(0, def_NameBackGround, OBJPROP_COLOR, def_GhostColor);            
199.             ObjectSetString(0, def_NameHLine, OBJPROP_NAME, macro_GhostName(def_NameHLine));
200.             ObjectSetString(0, def_NameBtnMove, OBJPROP_NAME, macro_GhostName(def_NameBtnMove));
201.             ObjectSetString(0, def_NameObjLabel, OBJPROP_NAME, macro_GhostName(def_NameObjLabel));
202.             ObjectSetString(0, def_NameBackGround, OBJPROP_NAME, macro_GhostName(def_NameBackGround));
203.             m_Info.priceGhost = MathAbs(m_Info.price);
204.             m_Info.szMsgGhost = "";
205.         }
206. //+------------------------------------------------------------------+
207.     public    :
208. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Este fragmento es algo más extenso. Aunque es bastante sencillo, contiene algunos puntos que merecen destacarse. Lo primero que observarás es que ya no aparece el código RemoveAllsObjects, implementado en el /es/articles/13457artículo 130 de esta serie. Esto demuestra que el código se modifica y mejora constantemente. Si has ido aplicando rigurosamente los fragmentos a medida que se publican, comprobarás que encajan perfectamente y generan el código fuente correspondiente a ese punto concreto del desarrollo.

En este fragmento quiero llamar tu atención sobre varios aspectos. En primer lugar, en la línea 178 asignamos al objeto OBJ_EDIT que actuará como sombra el mismo valor proporcionado cuando la prueba de la línea 174 da un resultado positivo. Sin embargo, observa que ese valor permanecerá siempre constante. Es importante que lo tengas presente, porque más adelante explicaré un detalle que, por ahora, introduce cierta limitación. No se trata de nada grave, pero presta atención a la línea 178.

Ahora debemos centrarnos en la sombra, que se crea al ejecutar el procedimiento de la línea 190. Esta línea resulta casi mágica, porque toma todos los objetos originales y los convierte en sombras o fantasmas. Observa que el proceso es similar al que vimos en el apartado anterior. Allí solo cambiábamos el nombre; aquí, además del nombre, también modificamos algunas propiedades de los objetos. En cualquier caso, al final obtenemos lo que necesitamos: los objetos que formarán la sombra. Presta atención a un detalle: casi todos los objetos se ven afectados, pero no modificamos el objeto que aparece en el gráfico como botón para cerrar la posición. Este se tratará en otro punto del código.

Quizá te estés preguntando algo: en el procedimiento de la línea 190, donde creamos la sombra, ¿no deberíamos asignar algún valor a m_Info.szMsgGhost? Al fin y al cabo, la variable recibe una cadena vacía, mientras que m_Info.priceGhost recibe el valor absoluto del precio. ¿Por qué ocurre esto? Si estamos creando las sombras, parecería lógico asignar un valor a m_Info.szMsgGhost para que puedan mostrarse en el gráfico.

Si has pensado de este modo, debo felicitarte, porque significa que estás comprendiendo cómo debería funcionar el código o cuál sería la implementación inicial más evidente. Sin embargo, las cosas no siempre resultan tan obvias. En ocasiones debemos forzar determinado comportamiento para obtener el resultado deseado. Para comprender por qué las variables reciben esos valores en las líneas 203 y 204, debemos analizar lo que ocurre en otros dos puntos del código. Para simplificar la explicación, los dividiré en dos fragmentos. El primero aparece a continuación.

208. //+------------------------------------------------------------------+
209.         C_ElementsTrade(const ulong ticket, string szSymbol, const EnumEvents ev, color _color, char digits, double ticksize, string szDescr = "\n", const bool IsBuy = true)
210.             :C_Mouse(0, "")
211.         {
212.             ZeroMemory(m_Info);
213.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
214.             m_Info._color = _color;
215.             m_Info.szDescr = szDescr;
216.             m_Info.bIsBuy = IsBuy;
217.             m_Info.Text.digits = digits;
218.             m_Info.tickSize = ticksize;
219.             m_Info.szSymbol = szSymbol;
220.         }
221. //+------------------------------------------------------------------+
222.         ~C_ElementsTrade()
223.         {
224.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
225.             ObjectsDeleteAll(0, m_Info.szPrefixName);
226.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
227.         }
228. //+------------------------------------------------------------------+
229. inline void UpdatePrice(const double open, const double price, const double vol = 0, const double var = 0, const double special = -1)
230.         {
231.             m_Info.sizeText = 0;
232.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
233.             ObjectsDeleteAll(0, m_Info.szPrefixName + "#");
234.             ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
235.             m_Info.volume = (vol > 0 ? vol : m_Info.volume);
236.             m_Info.var = (var > 0 ? var : m_Info.var);
237.             m_Info.tpsl = (special >= 0 ? special : m_Info.tpsl);
238.             if (price > 0)
239.             {
240.                 CreateLinePrice();
241.                 CreateButtonClose();
242.                 CreateObjectInfoText(def_NameObjLabel, m_Info._color);
243.             }
244.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
245.             m_Info.open = open;
246.             m_Info.price = (price > 0 ? price : -open);
247.             if (m_Info.ev == evMsgClosePositionEA)
248.             {
249.                 CreateObjectInfoText(def_NameVolume, clrBlack);
250.                 ObjectSetInteger(0, def_NameVolume, OBJPROP_BGCOLOR, clrViolet);                
251.                 ObjectSetString(0, def_NameVolume, OBJPROP_TEXT, DoubleToString(m_Info.volume, (MathRound(m_Info.volume) != m_Info.volume ? 2 : 0)));
252.                 AdjustDinamic(def_NameVolume, "88888" + (MathRound(m_Info.volume) != m_Info.volume ? ".88" : ""));
253.             };
254.             ChartChange();
255.         }
256. //+------------------------------------------------------------------+
257.         void ViewValue(const double profit, const bool Enabled = true)
258.         {
259.             string szTxt;
260.             color  _cor;
261.             static double memSL, memTP;
262.             
263.             if (Enabled)
264.             {
265.                 switch (m_Info.ViewMode)
266.                 {
267.                     case stInfos::eValue:
268.                         szTxt = StringFormat("%." + (string)m_Info.Text.digits + "f", MathAbs(profit));
269.                         break;
270.                     case stInfos::eFinance:
271.                         szTxt = StringFormat("$ %." + (string)m_Info.Text.digits + "f", (MathAbs(profit) / m_Info.var) * m_Info.volume);
272.                         break;
273.                     case stInfos::eTicks:
274.                         szTxt = StringFormat("%d", (uint)MathRound(MathAbs(profit) / m_Info.tickSize));
275.                         break;
276.                     case stInfos::ePercentage:
277.                         szTxt = StringFormat("%.2f%%", NormalizeDouble((MathAbs(profit) / (m_Info.open ? m_Info.open : m_Info.price)) * 100, 2));
278.                         break;
279.                 }
280.                 m_Info.szMsgGhost = ((m_Info.szMsgGhost == "") && (m_Info.ev != evMsgClosePositionEA) ? szTxt : m_Info.szMsgGhost);
281.                 ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, szTxt);
282.                 if (StringLen(szTxt) != m_Info.sizeText)
283.                 {
284.                     AdjustDinamic(def_NameObjLabel, szTxt);
285.                     m_Info.sizeText = StringLen(szTxt);
286.                 }
287.             }
288.             _cor = (m_Info.limit >= 0 ? clrPaleGreen : clrCoral);
289.             switch (m_Info.ev)
290.             {
291.                 case evMsgCloseTakeProfit:
292.                     memTP = (Enabled ? memTP : profit);
293.                     _cor = (m_Info.limit < memTP ? clrYellow : _cor);
294.                     break;
295.                 case evMsgCloseStopLoss:
296.                     memSL = (Enabled ? memSL : profit);
297.                     _cor = (m_Info.limit > memSL ? clrYellow : _cor);
298.                     break;
299.                 case evMsgClosePositionEA:
300.                     _cor = (profit >= 0 ? clrPaleGreen : clrCoral);
301.                     break;
302.             }
303.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, _cor);
304.         }
305. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade

Observa que ahora el destructor, situado en la línea 222, eliminará todos los objetos, como corresponde. Sin embargo, UpdatePrice, que aparece en la línea 229, ya no lo hará. En la versión anterior, tanto UpdatePrice como el destructor eliminaban todos los objetos. Conviene explicar un aspecto que hasta ahora había quedado implícito, pues se suponía que todos lo comprenderían fácilmente. Que UpdatePrice elimine los objetos antes de volver a crearlos solo tiene sentido si piensas en la posibilidad de que se hayan eliminado las líneas de stop loss o take profit. Probablemente imagines que, al llamar a UpdatePrice, simplemente actualizamos el nivel en el que se encuentran el stop loss o el take profit. Sin embargo, el funcionamiento real es algo diferente.

La idea es mostrar dónde estarían realmente esos puntos. No obstante, si alguno no existe en la posición, debemos eliminar del gráfico los objetos correspondientes. A primera vista, crear un único procedimiento para realizar esta tarea podría parecer una buena idea. Sin embargo, como siempre tendríamos que comprobar si la posición dispone de stop loss o take profit, resulta más práctico eliminar los objetos y volver a crearlos y colocarlos según corresponda. Por este motivo, UpdatePrice elimina los objetos del gráfico.

Sin embargo, como ahora estamos convirtiendo esos mismos objetos en sombras de otros que ya se encuentran en el gráfico, debemos limitar parcialmente su eliminación. ¿Recuerdas que mencioné que no crearíamos una sombra para el botón para cerrar la posición y que no era necesario eliminarlo en aquel punto concreto porque se quitaría en otro lugar? Pues bien, cuando se ejecute la línea 233 durante la creación de la sombra, ese botón se eliminará del gráfico.

Ahora presta atención. Cuando existe una línea de stop loss o take profit, el valor recibido en la línea 246 corresponde al precio en el que se encuentra ese nivel. En cambio, cuando uno o ambos valores no existen, la posición puede mantenerse abierta hasta que se cierre manualmente o hasta que el servidor la cierre, si se trata de una operación de day trading. En ese caso, m_Info.price contiene el precio de apertura con signo negativo. Como necesitamos conocer el nivel en el que debe dibujarse el objeto de movimiento, no podemos utilizar directamente ese valor negativo. Debemos convertirlo mediante la función MathAbs al crear las sombras, en la línea 190.

Veamos ahora cuándo recibe un valor m_Info.szMsgGhost. Observa la línea 280. Es allí donde asignamos su contenido. Sin embargo, quizá te preguntes lo siguiente: la función ViewValue se llama en más de un momento; ¿no hará esto que se ejecute la línea 94? No La razón es que utilizamos una comprobación doble para impedir que la línea 280 asigne un valor a m_Info.szMsgGhost cuando no corresponda. De este modo, la copia del valor que se muestra al usuario se mostrará en el momento en que se cree la sombra. Sin embargo, este mecanismo presenta un problema que resolveremos más adelante. Por ahora no debes preocuparte. Para que todo funcione correctamente, todavía debemos analizar el segundo fragmento que mencioné antes, que aparece a continuación.

305. //+------------------------------------------------------------------+
306.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
307.         {
308.             string sz0;
309.             long _lparam = lparam;
310.             double _dparam = dparam;
311.             
312.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
313.             switch (id)
314.             {
315.                 case (CHARTEVENT_KEYDOWN):
316.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
317.                     _lparam = (long) m_Info.ticket;
318.                     _dparam = 0;
319.                     EventChartCustom(0, evUpdate_Position, _lparam, 0, "");
320.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
321.                     if ((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev))
322.                     {
323.                         CreateGhost();
324.                         UpdatePrice(m_Info.open, GetPositionsMouse().Position.Price);
325.                     }else
326.                     {
327.                         ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
328.                         ObjectsDeleteAll(0, macro_GhostName(""));
329.                         ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
330.                     }
331.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
332.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
333.                     m_Info.bClick = false;
334.                 case CHARTEVENT_CHART_CHANGE:
335.                     ChartChange();
336.                     break;

Fragmento de C_ElementsTrade

Aquí muestro únicamente la parte que realmente necesitamos analizar. El resto del código permanece intacto. Sin embargo, es en este fragmento donde comienza a producirse todo el proceso. Observa la línea 321, donde realizamos una comprobación para determinar si estamos trabajando con el segmento correcto. Antes, simplemente enviábamos una solicitud a UpdatePrice. Ahora actuamos de una forma algo distinta, ya que es precisamente en este punto donde se implementa la sombra. Si la condición de la línea 321 se cumple, primero llamamos al procedimiento encargado de convertir los objetos actuales en objetos sombra. A continuación, enviamos la solicitud a UpdatePrice.

De este modo, la sombra queda en segundo plano y los nuevos objetos que manipularemos aparecen en primer plano. Así se crea la ilusión de que estamos sombreando el nivel anterior en el que se encontraba el precio. Al utilizar el sistema, observarás que la información anterior se conserva. Ese es precisamente el propósito de sombrear la ubicación anterior del precio.

Si la prueba da un resultado negativo, simplemente eliminamos los objetos de sombra presentes en el gráfico y devolvemos el precio a su ubicación anterior, donde se encontraba la sombra. La eliminación se realiza en la línea 328, mientras que el restablecimiento del precio a cargo del propio sistema que ya existía. Así, con muy pocos cambios en el código, obtenemos el resultado que aparece en la siguiente animación.

Probablemente sientas curiosidad por saber qué representa realmente esta sombra. La respuesta es muy sencilla: muestra dónde se encuentra el take profit o el stop loss en el servidor. Esta situación, que puede observarse claramente en el gráfico y en la animación anterior, evita la falsa impresión de que, al desplazar la línea de precio, también estamos desplazando ese nivel en el servidor. Los usuarios menos experimentados o quienes vean el sistema por primera vez podrían llegar a esa conclusión. Con esta representación, resulta mucho más claro y sencillo comprender lo que está ocurriendo.

Sin embargo, como ya indiqué, el sistema contiene un pequeño fallo que debemos corregir. Cuando seleccionas un segmento —ya sea el take profit o el stop loss— para modificar el nivel de precio y haces clic en el objeto OBJ_EDIT para cambiar el modo de visualización de los datos, la información se actualiza en todos los objetos. Sin embargo, la sombra no recibe esa actualización, lo que altera la interpretación de los datos. Este pequeño inconveniente puede hacer que algunos operadores o usuarios no se sientan cómodos utilizando el sistema. Aunque la solución es relativamente sencilla, exige modificar la estructura actual del código. Además, como también quiero eliminar el código duplicado, no abordaremos esa solución en este artículo.


Consideraciones finales

En este artículo te he mostrado algo que muchos considerarían bastante difícil de implementar: crear una sombra que indique al operador o usuario dónde se encuentra el precio anterior. En realidad, llamarlo «anterior» no es del todo correcto, ya que ese nivel continúa presente en la posición abierta y todavía puede activarse si se produce una negociación en ese nivel concreto. Aunque el sistema aún necesita algunos ajustes, como expliqué a lo largo del artículo, ya es lo bastante funcional como para utilizarlo.

Sin embargo, estos artículos sobre el sistema de repetición/simulador tienen una finalidad meramente didáctica. Por tanto, debes adoptar las precauciones necesarias si deseas utilizar alguna de estas aplicaciones en una cuenta real, ya que no se han optimizado específicamente para ello. En cambio, si realmente quieres comprender cómo funciona todo el sistema, puedes probarlo ampliamente en cuentas de demostración.

Para quienes deseen experimentar con el sistema, pero no sepan cómo generar los ejecutables, he dejado adjuntas las aplicaciones ya compiladas. Solo tienes que colocarlas junto a las que ya utilizas —o sustituir las versiones anteriores— y utilizarlas con una cuenta de demostración para observar el funcionamiento mostrado en la animación del apartado anterior. En el próximo artículo corregiremos el fallo de la sombra, que actualmente no refleja el cambio del modo de visualización efectuado en el indicador. También eliminaremos el código duplicado de la clase C_ElementsTrade. Será un paso interesante, y además estamos a un solo paso de comenzar a utilizar estas cuatro aplicaciones en nuestro sistema de repetición/simulador. Solo necesitamos corregir algunos detalles para poder retomar el trabajo en el servicio de repetición/simulador. No te pierdas el próximo artículo de esta serie.

Archivo Descripción
Experts\Expert Advisor.mq5
Muestra la interacción entre Chart Trade y el Asesor Experto. Es necesario Mouse Study para la interacción.
Indicators\Chart Trade.mq5 Crea la ventana en la que se configura la orden que se enviará. Es necesario Mouse Study para la interacción.
Indicators\Market Replay.mq5 Crea los controles necesarios para interactuar con el servicio de repetición/simulador. Es necesario Mouse Study para la interacción.
Indicators\Mouse Study.mq5 Permite la interacción entre los controles gráficos y el usuario. Es necesario tanto para utilizar el sistema de repetición/simulador como para operar en el mercado real.
Indicators\Order Indicator.mq5 Se encarga de mostrar las órdenes de mercado y permite interactuar con ellas y controlarlas.
Indicators\Position View.mq5 Se encarga de mostrar las posiciones de mercado y permite interactuar con ellas y controlarlas.
Services\Market Replay.mq5 Crea y mantiene el servicio de repetición y 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/13523

Archivos adjuntos |
Anexo.zip (779.24 KB)
Del básico al intermedio: Sobrecarga de operadores (II) Del básico al intermedio: Sobrecarga de operadores (II)
Este artículo puede parecer bastante confuso al principio por el contenido que presentaré. Sin embargo, he intentado presentar todo de la forma más sencilla y didáctica posible. Espero que comprendas lo que voy a mostrar aquí y que te resulte útil en algún momento.
Del básico al intermedio: Sobrecarga de operadores (I) Del básico al intermedio: Sobrecarga de operadores (I)
En este artículo comenzaremos a estudiar cómo se implementa la denominada sobrecarga de operadores. Primero veremos qué motiva esta implementación. También comprobaremos que las cosas no siempre son tan complicadas como parecen.
Simulación de mercado: La unión hace la fuerza (I) Simulación de mercado: La unión hace la fuerza (I)
Estamos llegando a la recta final. El desarrollo del sistema de repetición/simulador está casi terminado. Es cierto que aún tendremos que completar algunas cosas, pero, en comparación con todo lo que ya hemos hecho, implementar lo que falta será sencillo. Sin embargo, todo lo que se mostrará en este artículo deberá asimilarse y comprenderse adecuadamente. Quiero que tú, estimado lector y entusiasta...
De novato a experto: Trayectorias predictivas del precio De novato a experto: Trayectorias predictivas del precio
Los niveles de Fibonacci proporcionan un marco práctico que los mercados suelen respetar, destacando las zonas de precios donde es más probable que se produzcan reacciones. En este artículo, desarrollamos un asesor experto que aplica la lógica de retroceso de Fibonacci para anticipar posibles movimientos futuros y operar con retrocesos mediante órdenes pendientes. En este artículo recorremos todo el flujo de trabajo, desde la detección de oscilaciones hasta la representación gráfica de niveles, los controles de riesgo y la ejecución.