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

Simulación de mercado: Position View (XI)

MetaTrader 5Probador |
18 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos, bienvenidos a otro 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 (X), expliqué cómo podríamos seleccionar los objetos que estamos creando con la ayuda de MetaTrader 5. Hasta ese momento, la principal preocupación era encontrar una forma satisfactoria de seleccionar los objetos en el gráfico. Sin la ayuda de MetaTrader 5 y recurriendo a la propiedad ZOrder, esta tarea sería bastante complicada y difícil de resolver. Pero, como quedó demostrado en el artículo anterior, encontramos una forma bastante práctica y eficaz de usar MetaTrader 5 a nuestro favor.

Del mismo modo que conseguimos obtener ese resultado, también quedó claro que tenemos otro problema que resolver. Ese problema es, precisamente, la selección de las líneas de precio. Aunque sean relativamente más gruesas, sigue sin ser una tarea sencilla. Sobre todo, cuando necesitamos hacerlo con bastante rapidez y con las líneas muy cerca unas de otras.

Seleccionar correctamente la línea deseada y hacerlo lo más rápido posible es algo extremadamente necesario, pero también debemos pensarlo con cuidado. No solo vamos a seleccionar la línea, sino que también queremos permitir la creación de la línea de precio, ya sea de take profit o stop loss, directamente en el gráfico. Esta interacción deberá ser lo más simple posible para el operador. No desde el punto de vista del código, sino desde el punto de vista del operador.

Hace bastante tiempo, presenté en estos artículos un modelo que, en mi opinión, es bastante adecuado para nuestras necesidades. Sin embargo, aquel modelo estaba completamente vinculado al Asesor Experto, por lo que resultaba complicado extraerlo de su estructura interna. Pero, como es interesante para lo que necesitamos, explicaré cómo implementarlo. No obstante, ahora lo haremos en un indicador. Entonces, comencemos.


Creación de un botón para mover los precios

La idea de aquel modelo bastante antiguo era usar un botón específico para seleccionar la línea deseada. Además de permitirnos seleccionar la línea, ese botón también nos permitía crearla si no existía en el gráfico, entre otras pequeñas funciones que también desarrollaremos en este artículo. Así, lo primero que hay que hacer se muestra en el siguiente código.

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. //+------------------------------------------------------------------+
008. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
009. //+------------------------------------------------------------------+
010. #define def_PathBtns "Images\\Market Replay\\Orders\\"
011. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
012. #resource "\\" + def_Btn_Close;
013. //+------------------------------------------------------------------+
014. #include "..\Auxiliar\C_Mouse.mqh"
015. //+------------------------------------------------------------------+
016. class C_ElementsTrade : private C_Mouse
017. {
018.     private    :
019. //+------------------------------------------------------------------+
020.         struct st00
021.         {
022.             ulong         ticket;
023.             string        szPrefixName;
024.             EnumEvents    ev;
025.             double        price;
026.             bool          bClick;
027.             char          weight;
028.         }m_Info;
029. //+------------------------------------------------------------------+
030.         void UpdateViewPort(void)
031.         {
032.             int x, y;
033.             
034.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);            
035.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
036.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
037.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
038.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
039.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
040.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30);
041.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
042.         }
043. //+------------------------------------------------------------------+
044. inline void CreateLinePrice(const color _color, const string szDescr)
045.         {
046.             string szObj;
047.             
048.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault));
049.             macro_LineInFocus(false);
050.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _color);
051.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
052.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
053.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
054.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr);
055.         }
056. //+------------------------------------------------------------------+
057. inline void CreateBoxMove(const color _color)
058.         {
059.             string szObj;
060.             
061.             CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, _color, (EnumPriority)(ePriorityDefault));
062.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
063.             ObjectSetString(0, szObj, OBJPROP_TEXT, "u");
064.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17);
065.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
066.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21);
067.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23);
068.         }
069. //+------------------------------------------------------------------+
070. inline void CreateButtonClose(void)
071.         {
072.             string szObj;
073.             
074.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
075.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
076.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
077.         }
078. //+------------------------------------------------------------------+
079.     public    :
080. //+------------------------------------------------------------------+
081.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
082.             :C_Mouse(0, "")
083.         {        
084.             ZeroMemory(m_Info);
085.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
086.             CreateLinePrice(_color, szDescr);
087.             if (ev != evMsgClosePositionEA) CreateBoxMove(_color);
088.             CreateButtonClose();
089.         }
090. //+------------------------------------------------------------------+
091.         ~C_ElementsTrade()
092.         {
093.             ObjectsDeleteAll(0, m_Info.szPrefixName);
094.         }
095. //+------------------------------------------------------------------+
096. inline void UpdatePrice(const double price)
097.         {
098.             m_Info.price = price;
099.             UpdateViewPort();
100.         }
101. //+------------------------------------------------------------------+
102.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
103.         {
104.             string sz0;
105.             
106.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
107.             switch (id)
108.             {
109.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
110.                     macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev));
111.                     m_Info.bClick = false;
112.                 case CHARTEVENT_CHART_CHANGE:
113.                     UpdateViewPort();
114.                     break;
115.                 case CHARTEVENT_OBJECT_CLICK:
116.                     sz0 = GetPositionsMouse().szObjNameClick;
117.                     if (m_Info.bClick) switch (m_Info.ev)
118.                     {
119.                         case evMsgClosePositionEA:
120.                             if (sz0 == def_NameBtnClose)
121.                                 EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
122.                             break;
123.                         case evMsgCloseTakeProfit:
124.                             if (sz0 == def_NameBtnClose)
125.                                 EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
126.                             else if (sz0 == def_NameBtnMove)
127.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, "");
128.                             break;
129.                         case evMsgCloseStopLoss:
130.                             if (sz0 == def_NameBtnClose)
131.                                 EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
132.                             else if (sz0 == def_NameBtnMove)
133.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, "");
134.                             break;
135.                     }
136.                     m_Info.bClick = false;
137.                     break;
138.                 case CHARTEVENT_MOUSE_MOVE:
139.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
140.                     if (m_Info.bClick && (m_Info.weight > 1)) EventChartCustom(0, evMsgSetFocus, 0, 0, "");
141.                     break;
142.             }
143.         }
144. //+------------------------------------------------------------------+
145. };
146. //+------------------------------------------------------------------+
147. #undef macro_LineInFocus
148. //+------------------------------------------------------------------+
149. #undef def_Btn_Close
150. #undef def_PathBtns
151. //+------------------------------------------------------------------+
152. #undef def_NameBtnMove
153. #undef def_NameBtnClose
154. #undef def_NameHLine
155. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

Aquí empezamos a mejorar aún más la gestión de los objetos. Observa que en la línea 6 creamos una nueva definición. Esta definición le dará nombre al objeto que representará nuestro icono de desplazamiento. Mientras no esté presente, no podremos mover la línea. Cuando exista, la línea podrá moverse. Sin embargo, en este momento no nos preocuparemos por el desplazamiento. Primero veremos cómo gestionar la selección.

Observa que el código ha cambiado muy poco. Esto te permitirá entender realmente el proceso paso a paso, querido lector. Así, si lo deseas, podrás modificar fácilmente lo que te muestro.

En la línea 40, indicamos la ubicación de nuestro nuevo objeto en el eje X. Fíjate en que he dejado una pequeña separación entre este objeto y el botón de cierre. Ahora, presta atención. En la línea 35, modifiqué el desplazamiento de los objetos para que no se superpongan. De este modo, queda un pequeño espacio entre ellos, de modo que no tengamos problemas cuando este indicador esté en el gráfico.

Para facilitar aún más los cambios futuros, saqué del constructor los procedimientos que crean los objetos. Así se crearon los procedimientos CreateLinePrice y CreateButtonClose. El contenido de estos procedimientos estaba antes en el constructor. Por tanto, no entraré en más detalles sobre ellos. Sin embargo, el procedimiento CreateBoxMove es nuevo, por lo que merece una explicación básica. En este procedimiento, solo creamos un objeto del tipo OBJ_LABEL y le asignamos un texto. En realidad, un solo carácter. Este será el elemento que indicará si podemos seleccionar y, en consecuencia, mover la línea de precio. Como el código es muy simple, no creo que sea necesario explicarlo detalladamente.

Ahora, observa el siguiente detalle en el constructor. Para ser más exactos, mira la línea 87. En esa línea, comprobamos si el control creado es una línea de cierre de posición. Si no lo es, podemos crear el objeto de desplazamiento de la línea de precio. Muy bien, ahora podemos pasar al procedimiento DispatchMessage. En este procedimiento se produjeron cambios algo más importantes. Sin embargo, estos cambios buscan evitar la duplicación de código, ya que algunas cosas pueden manejarse en cascada.

Puedes verlo al observar el evento de la línea 109: en este evento, en la línea 111, asignaremos el valor false al estado del clic del mouse. Pero este evento de la línea 109 no finalizará donde muchos esperarían. En realidad, pasará al siguiente, CHARTEVENT_CHART_CHANGE, que aparece en la línea 112. Pero ¿por qué hacer o permitir esto? El motivo es sencillo. Cuando el grosor de la línea cambie, necesitamos repintarla en el gráfico. Si no dejáramos que el evento personalizado continuara hacia el evento de redibujado, tendríamos que añadir una llamada específica para redibujar la línea en el evento personalizado, algo que podemos descartar, ya que simplemente estamos encadenando el tratamiento de eventos en cascada.

Ahora, observa que en el evento CHARTEVENT_OBJECT_CLICK no comprobamos si se hizo clic en la línea de precio. Ahora verificamos si se hizo en el objeto de desplazamiento. Así que no pierdas el tiempo intentando seleccionar la línea de precio. Haz clic directamente en el pequeño diamante que aparece en ella. Así, la línea se seleccionará junto con el diamante.

Otra novedad se encuentra precisamente en el evento CHARTEVENT_MOUSE_MOVE. Observa que ahora, en la línea 140, se lleva a cabo una nueva comprobación. Esta comprobación sirve para verificar si hay alguna otra línea seleccionada en el gráfico. Si esto se cumple, se disparará un evento para quitar la selección. Este tipo de implementación es bastante curioso, ya que si hay alguna línea seleccionada y haces clic en cualquier parte del gráfico, la línea seleccionada se desmarcará. Y no tendremos que preocuparnos por cuál es la línea seleccionada. Todas las líneas se desmarcarán.

Muy bien, pero aunque lo que acabo de mostrar sirva para seleccionar o marcar una línea de precio, no permite hacer nada más útil. Ahora pensemos un poco. Te garantizo que será algo bastante interesante.

Al observar el código anterior y leer la explicación de su funcionamiento, ¿se te ocurre, querido lector, una manera simple, rápida y directa de mover estas líneas de precio? No importa si el servidor de trading lo acepta o no. Solo quiero que me digas si es posible mover las líneas con lo que acabamos de implementar. Recuerda que necesitamos añadir código al sistema para que esto sea realmente posible. La cuestión es si podemos hacerlo con la mínima cantidad de código posible. No quiero tener que escribir mucho código. Entonces, ¿es posible hacerlo o no?

Si respondiste que NO, es posible que no estés viendo realmente cómo funciona todo. Seguramente pensarás que no es posible hacerlo sin pensarlo con calma. Pero podemos hacerlo. Siempre que añadamos, claro está, un poco más de código a nuestro sistema principal. Recuerda que en los artículos anteriores ya añadimos dos mensajes nuevos al sistema. Sin embargo, esos mensajes no se implementaron porque no era prudente hacerlo en aquel momento. Ahora, sin embargo, tenemos una forma sencilla y rápida de seleccionar las líneas de precio. Así que podemos usar ese sistema de mensajes e implementarlo. Antes de hacerlo, quiero explicar qué vamos a hacer. Así podrás entender o intentar imaginar, antes de ver la solución, cómo vamos a implementarla.

Si sustituyes el archivo de cabecera C_ElementsTrade.mqh por el código que vimos al principio de este tema, verás que, al seleccionar una línea de precio y hacer clic en algún punto, la línea se desmarca. Esa es precisamente la idea. Cuando pidamos al indicador de posición que desmarque una línea de precio, este deberá enviar un mensaje al Asesor Experto para indicar que la línea marcada debe desplazarse a una nueva posición. Esta nueva posición será exactamente el punto donde se haga clic con el mouse. ¿Lo has entendido ahora?

Aun así, la idea no está del todo pulida. Pero no nos preocuparemos por eso ahora. Solo haremos lo que acabo de decir: cuando una línea de precio esté seleccionada y hagas clic en otro punto, esa línea deberá desplazarse hasta él. Esto se puede conseguir modificando el código del procedimiento DispatchMessage para que quede como se muestra a continuación.

138.     case CHARTEVENT_MOUSE_MOVE:
139.         m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
140.         if (m_Info.bClick && (m_Info.weight > 1))
141.         {
142.             switch (m_Info.ev)
143.             {
144.                 case evMsgCloseTakeProfit:
145.                     EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
146.                     break;
147.                 case evMsgCloseStopLoss:
148.                     EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
149.                     break;
150.             }
151.             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
152.         }
153.         break;

Fragmento de C_ElementsTrade.mqh

Observa lo que ocurrió aquí. Cuando añadas este fragmento al código de la clase C_ElementsTrade, podrás mover las líneas de take profit y stop loss con el indicador de mouse. Sin embargo, fíjate en el siguiente detalle. No estamos moviendo las líneas ni los objetos vinculados a ellas. Estamos haciendo exactamente lo que se había previsto. Es decir, marca una línea usando el objeto de desplazamiento. Lleva el indicador de mouse hasta una nueva posición y haz clic nuevamente. Cuando esto ocurra, la comprobación de la línea 140 deberá cumplirse en algún punto.

Cuando esto suceda, se realizará una selección en la línea 142 para disparar el evento más adecuado. En este caso, se podrá disparar el evento de la línea 145 si se había seleccionado la línea de take profit o el de la línea 148 si se había seleccionado la línea de stop loss. Observa que el mensaje que se enviará a MetaTrader 5 es prácticamente igual. Sin embargo, difiere en su segundo parámetro.

Este mensaje será reenviado por MetaTrader 5 al gráfico donde esté cargado el indicador y capturado por el Asesor Experto. Dentro del código de la clase C_Orders, incluida en el Asesor Experto, este mensaje se interpretará como una solicitud para cambiar la posición de la línea de precio, ya sea la de stop loss o la de take profit. En cuanto el servidor devuelva el resultado de nuestra solicitud, el Asesor Experto volverá a enviar un mensaje que será capturado por el indicador de posición, lo que forzará a este a mostrar el nuevo punto donde se encuentra la línea de precio. De esta manera, siempre que miremos el gráfico, sabremos exactamente dónde están las líneas en el servidor. Esto se debe a que siempre reflejamos exactamente lo que hay en el servidor. Así es como podemos mover fácilmente las líneas con la mínima cantidad de código posible.

Este sistema que acabamos de implementar funciona tan bien que incluso puedes usar el trailing stop, disponible en MetaTrader 5, para que la línea de stop loss se mueva y este indicador de posición la siga y se mantenga fiel a lo que exista en el servidor de trading.

Aunque este sistema funcione maravillosamente bien, todavía no hemos resuelto otro problema. El problema es el siguiente: ¿cómo podemos crear las líneas de take profit o stop loss si se eliminaron del servidor y, en consecuencia, del gráfico? Bien, esto parece mucho más complicado. Pero acompáñanos en la explicación y verás que es tan simple como lo que acabamos de hacer. Para separar los temas y evitar confusiones, pasaremos a un tema nuevo.


Sin líneas de stop loss y take profit. ¿Cómo volver a colocarlas?

Ahora tenemos un problema un poco diferente al del tema anterior. Este problema es el siguiente: Supongamos que eliminas los valores de stop loss y take profit del servidor. Pero, después de un tiempo, decides volver a colocarlos en su posición. Existen varias maneras de hacerlo. Sin embargo, aquí quiero mostrar cómo modificar el indicador de posición para volver a colocar el stop loss o el take profit directamente desde el gráfico.

Para ello, tendremos que modificar un poco más el indicador. Esto se debe a que, al eliminar el stop loss o el take profit de la posición, el indicador también elimina los elementos correspondientes a la línea de stop loss o de take profit. Ese es el comportamiento que tendremos que cambiar. Así podremos usar el mismo mecanismo que vimos en el tema anterior. Es decir, haz clic en uno de los puntos, ya sea stop loss o take profit. Lleva el indicador de mouse hasta el punto deseado y haz clic nuevamente. Así se cambia el valor límite para el cierre de la posición. Esta es la idea básica.

Pero, ¿cómo podemos hacerlo fácilmente? Quizá estés pensando en alguna solución bastante disparatada, mi querido lector. No sé si sería más sencilla o más complicada que la solución que se presentará. Pero, como en el tema anterior, la idea es modificar el código lo mínimo posible.

Muy bien, detengámonos nuevamente y pensemos un poco: ¿cuál sería la solución más simple a nuestro problema? Bien, considerando que queremos utilizar el mismo modelo que vimos en el tema anterior, la solución más sencilla sería mantener el objeto de desplazamiento de la línea de precio en el gráfico. Si has pensado eso, querido lector, has pensado lo mismo que yo. Pero, ¿dónde podemos colocar el objeto cuando no existe la línea de precio, ya sea la de take profit o la de stop loss? Bien, en este caso, pienso que la mejor solución sería colocar el objeto de desplazamiento justo en la línea donde se abrió la posición. No sé si piensas lo mismo, querido lector. Eres libre de pensar como quieras, siempre que, claro está, sepas lo que estás haciendo.

Entonces, hagámoslo de esta manera. Cuando se elimine la línea de stop loss o take profit de la posición, el indicador de desplazamiento se colocará junto a la línea de precio de apertura. Pero ahora tenemos un problema. Cuando se elimina esta misma línea, ya sea de stop loss o take profit, el indicador de posición también elimina los objetos asociados a ella. No queremos complicar aún más nuestro código de forma inútil, así que vamos a ver en qué parte del código el indicador elimina esos objetos. Esto se muestra en el siguiente fragmento.

45. //+------------------------------------------------------------------+
46.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
47.         {
48.             double value;
49.             
50.             if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
51.             if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
52.             if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam);
53.             switch (id)
54.             {
55.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
56.                     if (lparam != m_Infos.ticket) return;
57.                     if (!PositionSelectByTicket(m_Infos.ticket))
58.                     {
59.                         ChartIndicatorDelete(0, 0, m_Infos.szShortName);
60.                         return;
61.                     };
62.                     if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket));
63.                     if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket));
64.                     if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket));
65.                     (*Open).UpdatePrice(PositionGetDouble(POSITION_PRICE_OPEN));
66.                     if ((value = PositionGetDouble(POSITION_TP)) > 0) (*Take).UpdatePrice(value); else
67.                     {
68.                         delete Take;
69.                         Take = NULL;
70.                     }
71.                     if ((value = PositionGetDouble(POSITION_SL)) > 0) (*Stop).UpdatePrice(value);    else
72.                     {
73.                         delete Stop;
74.                         Stop = NULL;
75.                     }
76.                     break;
77.             }
78.             ChartRedraw();
79.         }
80. //+------------------------------------------------------------------+

Fragmento de C_IndicatorPosition.mqh

Este fragmento forma parte de la clase C_IndicatorPosition. Es el código original de dicha clase. Bien, ahora observa los siguientes puntos. En las líneas 66 y 71 tenemos dos comprobaciones: una para el take profit y otra para el stop loss. Ahora, presta atención para entender lo que tenemos que hacer. Estas comprobaciones le comunican al indicador de posición que debemos eliminar los punteros a la clase C_ElementsTrade. Esto ocurre cuando el valor de la posición es igual o menor que cero. Creo que puedes entenderlo claramente, querido lector y entusiasta.

Sin embargo, aquí hay un punto en el que tendremos que modificar el código. Piensa un poco. Si, en lugar de eliminar el puntero a la clase C_ElementsTrade, pasamos el valor de las líneas de take profit y de stop loss a dicha clase, podremos crear algún mecanismo que nos permita hacer lo que deseamos. Entonces, este mismo fragmento deberá modificarse para implementar dicho mecanismo. Esta modificación se muestra a continuación.

45. //+------------------------------------------------------------------+
46.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
47.         {
48.             double value;
49.             
50.             if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
51.             if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
52.             if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam);
53.             switch (id)
54.             {
55.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
56.                     if (lparam != m_Infos.ticket) return;
57.                     if (!PositionSelectByTicket(m_Infos.ticket))
58.                     {
59.                         ChartIndicatorDelete(0, 0, m_Infos.szShortName);
60.                         return;
61.                     };
62.                     if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket));
63.                     if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket));
64.                     if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket));
65.                     (*Open).UpdatePrice(0, value = PositionGetDouble(POSITION_PRICE_OPEN));
66.                     (*Take).UpdatePrice(value, PositionGetDouble(POSITION_TP));
67.                     (*Stop).UpdatePrice(value, PositionGetDouble(POSITION_SL));
68.                     break;
69.             }
70.             ChartRedraw();
71.         }
72. //+------------------------------------------------------------------+

Fragmento de C_IndicatorPosition.mqh

Observa que el código apenas ha cambiado. Sin embargo, lo que hicimos fue eliminar las comprobaciones que se estaban realizando. Ahora, independientemente del valor del stop loss o del take profit, este se pasará a la clase C_ElementsTrade. De este modo, podremos manejar adecuadamente los objetos de la línea de precio.

Perfecto, ahora veamos cómo está el código en la clase C_ElementsTrade. El código original se muestra en el siguiente fragmento.

095. //+------------------------------------------------------------------+
096. inline void UpdatePrice(const double price)
097.         {
098.             m_Info.price = price;
099.             UpdateViewPort();
100.         }
101. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade.mqh

Pues bien, ahora centrémonos en el fragmento anterior. Aquí es donde ocurrirá la magia. Sin embargo, observa que originalmente esperábamos recibir un solo parámetro. No obstante, en la modificación realizada en la clase C_IndicatorPosition, se pueden ver dos parámetros que se están pasando a este procedimiento. Esta será la primera modificación. Pero antes de ver cómo deberá quedar el código, reunamos algunos detalles más. Bien, el primer argumento recibido indicará dónde se encuentra la línea de precio de apertura de la posición. El segundo es el precio de la línea que se va a crear.

Ahora, piensa un poco. Solo necesitamos eliminar los objetos innecesarios, que en este momento son el objeto que representa la línea de precio y el botón de cierre, y mantener el botón de desplazamiento. Sin embargo, este objeto que representa el desplazamiento se reubicará en una nueva posición. Esa nueva posición es el precio de apertura de la posición. Así, la manera más simple de implementar este ajuste es modificar el fragmento anterior para que quede como se muestra a continuación.

095. //+------------------------------------------------------------------+
096. inline void UpdatePrice(const double open, const double price)
097.         {
098.             m_Info.price = (price > 0 ? price : open);
099.             if (price == 0)
100.             {
101.                 ObjectDelete(0, def_NameBtnClose);
102.                 ObjectDelete(0, def_NameHLine);
103.             }
104.             UpdateViewPort();
105.         }
106. //+------------------------------------------------------------------+

Fragmento de C_ElementsTrade.mqh

Este fragmento permitiría que nuestro indicador de posición funcionara como se esperaba, pero hay un pequeño inconveniente. Observa que, cuando el operador elimina la línea de stop loss o take profit de la posición abierta, el indicador de posición también elimina la línea horizontal y el botón de cierre. Esto se debe a las líneas 101 y 102 del fragmento anterior. No obstante, cuando el operador solicite volver a colocar la línea de stop loss o de take profit en la posición abierta, solo se moverá correctamente el botón de desplazamiento. Los demás objetos se habrán eliminado de la lista de objetos. Y no aparecerán en el gráfico en ningún caso. El resultado se muestra en la siguiente imagen:

Esto no es un gran problema. Es solo un inconveniente que debemos corregir. Corregirlo es tan sencillo como lo que hemos hecho hasta ahora. Sin embargo, tendremos que realizar un cambio algo mayor en el código de la clase. A continuación se muestra el código final, ya corregido.

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. //+------------------------------------------------------------------+
008. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
009. //+------------------------------------------------------------------+
010. #define def_PathBtns "Images\\Market Replay\\Orders\\"
011. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
012. #resource "\\" + def_Btn_Close;
013. //+------------------------------------------------------------------+
014. #include "..\Auxiliar\C_Mouse.mqh"
015. //+------------------------------------------------------------------+
016. class C_ElementsTrade : private C_Mouse
017. {
018.     private    :
019. //+------------------------------------------------------------------+
020.         struct st00
021.         {
022.             ulong       ticket;
023.             string      szPrefixName,
024.                         szDescr;
025.             EnumEvents  ev;
026.             double      price;
027.             bool        bClick;
028.             char        weight;
029.             color       _color;
030.         }m_Info;
031. //+------------------------------------------------------------------+
032.         void UpdateViewPort(void)
033.         {
034.             int x, y;
035.             
036.             ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y);            
037.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
038.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
039.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
040.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
041.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
042.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30);
043.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
044.         }
045. //+------------------------------------------------------------------+
046. inline void CreateLinePrice(void)
047.         {
048.             string szObj;
049.             
050.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
051.             macro_LineInFocus(false);
052.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
053.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
054.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
055.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
056.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
057.         }
058. //+------------------------------------------------------------------+
059. inline void CreateBoxMove(void)
060.         {
061.             string szObj;
062.             
063.             CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
064.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
065.             ObjectSetString(0, szObj, OBJPROP_TEXT, "u");
066.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17);
067.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
068.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21);
069.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23);
070.         }
071. //+------------------------------------------------------------------+
072. inline void CreateButtonClose(void)
073.         {
074.             string szObj;
075.             
076.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
077.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
078.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
079.         }
080. //+------------------------------------------------------------------+
081.     public    :
082. //+------------------------------------------------------------------+
083.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n")
084.             :C_Mouse(0, "")
085.         {        
086.             ZeroMemory(m_Info);
087.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
088.             m_Info._color = _color;
089.             m_Info.szDescr = szDescr;
090.         }
091. //+------------------------------------------------------------------+
092.         ~C_ElementsTrade()
093.         {
094.             ObjectsDeleteAll(0, m_Info.szPrefixName);
095.         }
096. //+------------------------------------------------------------------+
097. inline void UpdatePrice(const double open, const double price)
098.         {
099.             m_Info.price = (price > 0 ? price : open);
100.             if (price > 0)
101.             {
102.                 CreateLinePrice();
103.                 CreateButtonClose();
104.             }else
105.                 ObjectsDeleteAll(0, m_Info.szPrefixName);
106.             if (m_Info.ev != evMsgClosePositionEA) CreateBoxMove();
107.             UpdateViewPort();
108.         }
109. //+------------------------------------------------------------------+
110.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
111.         {
112.             string sz0;
113.             
114.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
115.             switch (id)
116.             {
117.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
118.                     macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev));
119.                     m_Info.bClick = false;
120.                 case CHARTEVENT_CHART_CHANGE:
121.                     UpdateViewPort();
122.                     break;
123.                 case CHARTEVENT_OBJECT_CLICK:
124.                     sz0 = GetPositionsMouse().szObjNameClick;
125.                     if (m_Info.bClick) switch (m_Info.ev)
126.                     {
127.                         case evMsgClosePositionEA:
128.                             if (sz0 == def_NameBtnClose)
129.                                 EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
130.                             break;
131.                         case evMsgCloseTakeProfit:
132.                             if (sz0 == def_NameBtnClose)
133.                                 EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
134.                             else if (sz0 == def_NameBtnMove)
135.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, "");
136.                             break;
137.                         case evMsgCloseStopLoss:
138.                             if (sz0 == def_NameBtnClose)
139.                                 EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
140.                             else if (sz0 == def_NameBtnMove)
141.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, "");
142.                             break;
143.                     }
144.                     m_Info.bClick = false;
145.                     break;
146.                 case CHARTEVENT_MOUSE_MOVE:
147.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
148.                     if (m_Info.bClick && (m_Info.weight > 1))
149.                     {
150.                         switch (m_Info.ev)
151.                         {
152.                             case evMsgCloseTakeProfit:
153.                                 EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
154.                                 break;
155.                             case evMsgCloseStopLoss:
156.                                 EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
157.                                 break;
158.                         }
159.                         EventChartCustom(0, evMsgSetFocus, 0, 0, "");
160.                     }
161.                     break;
162.             }
163.         }
164. //+------------------------------------------------------------------+
165. };
166. //+------------------------------------------------------------------+
167. #undef macro_LineInFocus
168. //+------------------------------------------------------------------+
169. #undef def_Btn_Close
170. #undef def_PathBtns
171. //+------------------------------------------------------------------+
172. #undef def_NameBtnMove
173. #undef def_NameBtnClose
174. #undef def_NameHLine
175. //+------------------------------------------------------------------+

C_ElementsTrade.mqh

El resultado de ejecutar este código se muestra en la siguiente animación.

Observa que ahora el sistema funciona perfectamente. Tal y como se esperaba. Como el código ha sufrido algunos cambios, muchos de ellos muy simples, no es necesario destacarlos especialmente. Sin embargo, quiero que prestes atención a dos puntos de este código en especial, mi querido lector. El primero es el constructor. Observa que es un poco diferente de lo que se veía en la versión anterior. El segundo es el procedimiento UpdatePrice. Observa que los procedimientos que creaban los objetos, que antes estaban en el constructor, ahora están en este procedimiento. Como el código es bastante simple, no es necesario entrar en detalles. De hecho, lo único que se hizo fue quitar lo que se hacía en el constructor y pasarlo a este procedimiento. Nada especialmente espectacular.

Sin embargo, mi querido lector, quiero que dirijas tu atención a otra cosa. En concreto, quiero que prestes atención a la animación anterior. Y respóndeme sinceramente. Si, como operador, intentaras usar este sistema que acabamos de implementar, ¿dudarías si estás ajustando el take profit o el stop loss? ¿Sabrías si tendrías que llevar la línea del indicador de mouse hacia un precio mayor o menor para volver a colocar la línea de precio en el servidor? Quizá esta última pregunta no haya quedado lo suficientemente clara. Intentemos aclararla un poco para que puedas comprender el problema que aún tenemos que resolver.

Al observar solo la animación, se puede apreciar claramente que estoy en una posición vendida. Esto se debe a que la línea de take profit está por debajo del precio de apertura de la posición. Bien, pero supongamos que, como operador, hubieras eliminado ambas líneas del gráfico. O, mejor dicho, que las hubieras eliminado de la posición registrada en el servidor. Entonces, al mirar esta misma animación y observar solo el indicador de posición, no sabrías si estamos vendidos o comprados. Este es el primer punto.

El segundo punto es el siguiente: en caso de que hayamos vendido y los valores de take profit y stop loss no estén definidos en la posición, es decir, que ambos objetos que permiten mover la línea de precio se encuentren en el precio de apertura, ¿cómo podrías saber, solo mirando el gráfico, si debes colocar la línea de stop loss por encima o por debajo del precio actual? Es difícil responder a esta pregunta, ¿no? Sin embargo, tenemos una solución bastante sencilla para este problema. Como puedes observar, en esta clase C_ElementsTrade no accedemos en ningún momento a datos de la posición. Los datos proceden de la clase C_IndicatorPosition. Y no quiero contaminar la clase C_ElementsTrade con datos que, en el futuro, tendrían que eliminarse de ella. Esto es porque también la usaremos cuando trabajemos con órdenes pendientes. Por eso será necesario esforzarnos un poco más. Lo veremos en el próximo artículo.


Consideraciones finales

En este artículo, te he mostrado, querido y estimado lector, cómo puedes modificar el indicador de posición para que sea capaz de hacer muchas más cosas de las que originalmente podía hacer, sin mucho esfuerzo. Vimos cómo incorporar la capacidad de mover los precios y crear líneas de precio directamente en el gráfico. Algo que muchos considerarían extremadamente complicado y difícil de resolver. Sin embargo, habrás notado que lo hicimos todo con mucha facilidad y con un mínimo de esfuerzo. Lo único que hizo falta fue detenerse y pensar un poco.

Ese es precisamente el propósito de esta serie de artículos. No estoy aquí para enseñarte a crear una aplicación. Sin embargo, aprender programación es mucho más motivador e interesante cuando vemos que estamos creando algo. Espero que, querido lector, que estás iniciándote en este fantástico mundo de la programación, puedas entender el objetivo de estos artículos y que te sirvan de buena base teórica y motivacional para seguir aprendiendo cada vez más. Porque no existen problemas imposibles. Lo que sí existen son personas que no logran ver una solución sencilla para grandes problemas.

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 interactuar con 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.mq5Responsable de mostrar las órdenes de mercado, permitiendo interactuar con ellas y controlarlas.
Indicators\Position View.mq5Responsable de mostrar las posiciones de mercado, permitiendo interactuar con ellas y controlarlas.
Services\Market Replay.mq5Crea y mantiene el servicio de replay 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/13295

Archivos adjuntos |
Anexo.zip (779.24 KB)
Del básico al intermedio: Colas, listas y árboles (IV) Del básico al intermedio: Colas, listas y árboles (IV)
En este artículo, finalizaremos la parte relativa a la implementación y explicación de una lista enlazada. Sin embargo, la implementación mostrada aquí no incluirá cierto detalle que podemos implementar en una lista enlazada. Esto se verá más adelante, en otro artículo.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 27): Herramienta de barrido de liquidez con filtro de media móvil Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 27): Herramienta de barrido de liquidez con filtro de media móvil
Comprender las sutiles dinámicas que subyacen a los movimientos de los precios puede proporcionarle una ventaja decisiva. Uno de estos fenómenos es el «liquidity sweep», una estrategia deliberada que utilizan los grandes operadores, especialmente las instituciones, para empujar los precios a través de niveles clave de soporte o resistencia. Estos niveles suelen coincidir con concentraciones de órdenes stop-loss de traders minoristas, lo que crea zonas de liquidez que los grandes operadores pueden aprovechar para entrar en posiciones voluminosas o salir de ellas con un deslizamiento mínimo.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
De novato a experto: Desmitificando los niveles ocultos de retroceso de Fibonacci De novato a experto: Desmitificando los niveles ocultos de retroceso de Fibonacci
En este artículo, analizamos un enfoque basado en datos para descubrir y validar niveles de retroceso de Fibonacci no estándar que los mercados podrían respetar. Presentamos un flujo de trabajo completo diseñado específicamente para su implementación en MQL5, que comienza con la recopilación de datos y la detección de barras o swings, y se extiende hasta la agrupación en clústeres, las pruebas de hipótesis estadísticas, el backtesting y la integración en una herramienta de Fibonacci de MetaTrader 5. El objetivo es crear un proceso reproducible que transforme las observaciones anecdóticas en señales de trading estadísticamente defendibles.