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

Simulación de mercado: Position View (XIII)

MetaTrader 5Probador |
31 0
Daniel Jose
Daniel Jose

Introducción

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

En el artículo anterior, Simulación de mercado: Position View (XII), mejoramos el indicador de posición para que resultara más útil y agradable de usar. Aun así, todavía no es una herramienta plenamente práctica. Ya incorpora funciones que muchos podrían juzgar muy difíciles de implementar, aunque las añadimos de manera bastante sencilla. Ahora introduciremos algunos cambios menores para aumentar su utilidad.

No te preocupes: los cambios serán fáciles de entender y sentarán las bases para crear una solución más elaborada y funcional. Comencemos.


Eliminación de la clase C_IndicatorPosition

Antes de eliminar la clase C_IndicatorPosition del código final, conviene explicar el motivo. Queremos eliminar la capa de intermediación entre el indicador y la clase C_ElementsTrade. Tal vez te preguntes por qué modificamos la arquitectura ahora. Y es bastante sencillo. Hasta entonces no se había definido por completo el funcionamiento del indicador de posición. Mantuvimos temporalmente la clase C_IndicatorPosition como capa de desacoplamiento para aislar la implementación de posibles fallos o modificaciones radicales, tal como muestra la siguiente imagen.

En el esquema anterior, la línea negra representa la comunicación entre el indicador y la clase C_IndicatorPosition. De esta clase salen tres líneas, roja, azul y verde, que corresponden a los componentes que deben trazarse en el gráfico: el stop loss, el precio de apertura y el take profit, respectivamente. Aunque esta estructura ha sido útil hasta ahora, en la siguiente etapa nos entorpecerá. Por eso eliminaremos la clase C_IndicatorPosition del proyecto e integraremos en el indicador los métodos y las variables miembro que ahora define esa clase, como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Positions.ico"
04. #property description "Indicator for tracking an open position on the server."
05. #property description "This should preferably be used together with an Expert Advisor."
06. #property description "For more details see the same article."
07. #property version   "1.125"
08. #property link "https://www.mql5.com/pt/articles/13356"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. //+------------------------------------------------------------------+
12. #define def_ShortName "Position View"
13. //+------------------------------------------------------------------+
14. #include <Market Replay\Order System\C_ElementsTrade.mqh>
15. #include <Market Replay\Defines.mqh>
16. //+------------------------------------------------------------------+
17. input ulong user00 = 0;        //For Expert Advisor use
18. //+------------------------------------------------------------------+
19. struct st00
20. {
21.     ulong  ticket;
22.     string szShortName;
23. }m_Infos;
24. //+------------------------------------------------------------------+
25. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL;
26. //+------------------------------------------------------------------+
27. bool CheckCatch(ulong ticket)
28. {
29.     ZeroMemory(m_Infos);
30.     m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket);
31.     if (!PositionSelectByTicket(m_Infos.ticket)) return false;
32.     if (ObjectFind(0, m_Infos.szShortName) >= 0)
33.     {
34.         m_Infos.ticket = 0;
35.         return false;
36.     }
37.     IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
38.     EventChartCustom(0, evUpdate_Position, ticket, 0, "");
39.             
40.     return true;
41. }
42. //+------------------------------------------------------------------+
43. int OnInit()
44. {
45.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
46.     if (!CheckCatch(user00))
47.     {
48.         ChartIndicatorDelete(0, 0, def_ShortName);
49.         return INIT_FAILED;
50.     }
51. 
52.     return INIT_SUCCEEDED;
53. }
54. //+------------------------------------------------------------------+
55. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
56. {
57.     return rates_total;
58. }
59. //+------------------------------------------------------------------+
60. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
61. {
62.     double value;
63.             
64.     if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam);
65.     if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam);
66.     if (Stop != NULL)    (*Stop).DispatchMessage(id, lparam, dparam, sparam);
67.     switch (id)
68.     {
69.         case CHARTEVENT_CUSTOM + evUpdate_Position:
70.             if (lparam != m_Infos.ticket) return;
71.             if (!PositionSelectByTicket(m_Infos.ticket))
72.             {
73.                 ChartIndicatorDelete(0, 0, m_Infos.szShortName);
74.                 return;
75.             };
76.             if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, StringFormat("%I64u : Position opening price.", m_Infos.ticket), PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
77.             if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, StringFormat("%I64u : Take Profit price.", m_Infos.ticket));
78.             if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket));
79.             (*Open).UpdatePrice(0, value = PositionGetDouble(POSITION_PRICE_OPEN));
80.             (*Take).UpdatePrice(value, PositionGetDouble(POSITION_TP));
81.             (*Stop).UpdatePrice(value, PositionGetDouble(POSITION_SL));
82.             break;
83.     }
84.     ChartRedraw();
85. };
86. //+------------------------------------------------------------------+
87. void OnDeinit(const int reason)
88. {
89.     delete Open;
90.     delete Take;
91.     delete Stop;
92. }
93. //+------------------------------------------------------------------+

Código fuente del indicador de posición

El código anterior ya incorpora los métodos, las variables y la lógica de actualización que antes definía C_IndicatorPosition. El siguiente esquema refleja la nueva estructura.

A primera vista, la arquitectura parece más sencilla, mientras que el código del indicador puede parecer más complejo. Ninguna de las dos impresiones es exacta: la distribución de responsabilidades entre el indicador y las clases auxiliares no se ha simplificado ni complicado, sino que solo se ha reorganizado.

Este cambio es necesario porque la nueva etiqueta de beneficio o pérdida debe acceder directamente al componente que representa el precio de apertura. Ahora mostraremos el resultado de la posición, expresado como importe o como número de ticks de beneficio o pérdida. Así, el operador podrá decidir con mayor facilidad si conviene cerrarla.

Vaya, ahora sí que la cosa se ha puesto interesante. ¿Podríamos conservar la clase C_IndicatorPosition? Sí, pero no compensa añadirle una función únicamente para acceder al puntero Open. Este puntero referencia la línea y la etiqueta gráficas que marcan el precio de apertura de la posición. Aclarado el motivo del cambio, pasemos al siguiente apartado.


Entendiendo la indicación de beneficio o pérdida

Por ahora omitiremos otras métricas de la posición, como el volumen negociado y el resultado monetario, y mostraremos solo si la posición genera beneficios o pérdidas. Primero calcularemos la diferencia entre el precio de apertura y el precio de cierre, expresada como cantidad de puntos; más adelante convertiremos esa diferencia en un resultado monetario, ya que el cálculo financiero requiere modificaciones adicionales.

Pensemos ahora en cómo obtener el precio actual del símbolo. No basta con usar la función OnCalculate, porque el operador podría trabajar con un sistema de órdenes cruzadas y, en ese caso, esta función quizá no fuera la opción ideal. Aun así, supondremos que el operador trabaja con un símbolo similar. Por ejemplo, puedes consultar el historial del contrato completo del dólar para operar el contrato vigente del mini dólar. En ese caso, la diferencia entre ambos símbolos no plantea un problema.

Para aclararlo: en B3 (Bolsa de Brasil) existen contratos que no son negociables, como el historial de los contratos de futuros. Los operadores profesionales suelen utilizar ese historial como referencia para operar el contrato vigente. También puede aplicarse una operativa multisímbolo: se analiza un instrumento y se ejecutan las órdenes sobre otro mediante un sistema de órdenes cruzadas. Para quienes operan con pares de divisas en FOREX, esta forma de separar el símbolo analizado del símbolo negociado quizá resulte menos familiar.

También podemos crear un gráfico de ratio entre instrumentos. En FOREX, operar a partir de ese gráfico exige enviar las órdenes al par negociado mediante un sistema de órdenes cruzadas. Por tanto, esta explicación puede resultarte útil si comprendes con precisión cómo funciona esta operativa. Dejaremos esta operativa multisímbolo y el uso de gráficos de ratio para otra ocasión y volveremos al tema que nos ocupa.

Usaremos, por tanto, la función OnCalculate para mostrar si obtenemos beneficios o pérdidas. Sin embargo, no basta con tomar el precio actual, aunque a primera vista parezca la solución más sencilla. Presta atención a la diferencia entre el precio de entrada y la cotización disponible para cerrar la posición. Cuando compramos, ejecutamos la orden contra la liquidez disponible en el ASK; cuando vendemos, la ejecutamos contra la liquidez disponible en el BID. Para cerrar una posición de compra debemos vender contra el BID. Del mismo modo, para cerrar una posición de venta debemos comprar contra el ASK.

En los artículos anteriores sobre el sistema de replay/simulador vimos dos formas de trazado: el trazado de LAST y el trazado de BID. Si este concepto no te resulta familiar, consulta esos artículos. Con independencia del tipo de trazado, el parámetro price de la función OnCalculate contiene siempre el último precio. Aunque este comportamiento puede modificarse, descartaremos esa opción porque nos interesa el último precio, no un promedio. En el trazado de LAST, price corresponde al precio de la última operación ejecutada, que puede ser BID o ASK. En el trazado de BID, representa exactamente el precio BID, es decir, el mejor precio de compra cotizado.

Aquí surge el primer problema. El trazado de LAST suele utilizarse en cuentas tipo NETTING, mientras que el trazado de BID es habitual en FOREX. Quizá pienses que bastaría con leer POSITION_PROFIT y mostrar el beneficio o la pérdida calculados por esa propiedad mediante una etiqueta gráfica de texto. En principio parece razonable, pero este enfoque puede indicar un beneficio cuando el cierre de la posición produciría una pérdida. La causa es el SPREAD.

Es posible que no tengas claro cómo se calcula POSITION_PROFIT. El problema es que esta propiedad no distingue si la posición es de compra o de venta y, además, no incorpora el SPREAD en el cálculo. Ese spread puede erosionar considerablemente el capital. Puedes ver un beneficio en POSITION_PROFIT, cerrar la posición y descubrir después que la operación produjo una pérdida.

Por eso descartaremos POSITION_PROFIT y calcularemos directamente el resultado del cierre. Si la posición es de compra, comprobaremos el resultado de cerrarla mediante una venta ejecutada contra el BID; si es de venta, calcularemos el resultado de una compra ejecutada contra el ASK. Si utilizas este indicador con un sistema de órdenes cruzadas, procura elegir un símbolo con buena liquidez. Así, las variaciones de las cotizaciones reflejarán mejor el comportamiento real del mercado.


Añadimos la indicación de beneficio o pérdida

Ya conocemos el objetivo y su justificación. Ahora podemos programarlo mediante una demostración sencilla. Para ello, modificaremos ligeramente el indicador. El siguiente bloque contiene el código completo actualizado.

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

Código fuente del Indicador de posición

Veamos los cambios. La estructura de la línea 19 incorpora nuevos campos para almacenar el nombre del símbolo, la dirección de la posición y el precio de apertura. Primero inicializamos el nombre del símbolo, como indica la línea 40. Lo obtenemos de la posición, y no de la constante _Symbol, porque el indicador puede trabajar con un símbolo distinto del que aparece en el gráfico. Una vez obtenido el nombre, pasaremos de OnCalculate a OnChartEvent, situada en la línea 71.

En esta función inicializamos los demás campos de la estructura: m_Infos.bIsBuy en la línea 85 y m_Infos.priceOpen en la línea 88. Con esto ya están inicializados todos los campos que requiere el cálculo. Antes de crear la indicación gráfica, conviene comprobar el funcionamiento del cálculo. Pruébalo en esta fase del desarrollo y después ve a la línea 59, donde se encuentra la función OnCalculate.

Genial, la lógica de OnCalculate es sencilla. En las líneas 63 y 64 solicitamos a MetaTrader 5 las cotizaciones ASK y BID. La línea 66 muestra en la esquina superior izquierda del gráfico las cotizaciones que intervienen en el cálculo. Allí aparecen el mejor precio de compra (BID), el mejor precio de venta (ASK) y el resultado que obtendríamos al cerrar la operación en ese instante. El cálculo depende del tipo de posición: un resultado negativo indica una pérdida al cierre; uno positivo, un beneficio. Algo muy sencillo y directo, no hay ningún tipo de complicación ni trampa.

Una vez comprobado el funcionamiento, podemos mostrar el resultado PROFIT en una etiqueta gráfica vinculada a la línea del precio de apertura. Conviene aclarar que la implementación admite operaciones de precio medio y cambios en el volumen negociado. Si cambia el precio de apertura, el indicador se adapta automáticamente y refleja el precio registrado por el servidor de trading. Por ahora no calculamos el importe financiero, sino el desplazamiento del precio expresado como cantidad de ticks, ya sea a favor o en contra de la posición.

Muy bien, para dejar las cosas claras, pasemos al siguiente apartado.


Creamos la indicación en la línea de precio

La implementación de esta indicación es sencilla: basta con crear una etiqueta gráfica de texto, configurar sus propiedades y colocarla junto a la línea del precio de apertura. Así de simple. Dejaremos por ahora el código del indicador y trabajaremos con la clase C_ElementsTrade, donde implementaremos esa etiqueta. El siguiente bloque contiene el código completo actualizado de la clase.

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. //+------------------------------------------------------------------+
010. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1));
011. //+------------------------------------------------------------------+
012. #define def_PathBtns "Images\\Market Replay\\Orders\\"
013. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp"
014. #resource "\\" + def_Btn_Close;
015. //+------------------------------------------------------------------+
016. #include "..\Auxiliar\C_Mouse.mqh"
017. //+------------------------------------------------------------------+
018. class C_ElementsTrade : private C_Mouse
019. {
020.     private    :
021. //+------------------------------------------------------------------+
022.         struct st00
023.         {
024.             ulong       ticket;
025.             string      szPrefixName,
026.                         szDescr;
027.             EnumEvents  ev;
028.             double      price;
029.             bool        bClick,
030.                         bIsBuy;
031.             char        weight,
032.                         digits;
033.             color       _color;
034.         }m_Info;
035. //+------------------------------------------------------------------+
036.         void UpdateViewPort(const double price)
037.         {
038.             int x, y;
039.             
040.             ChartTimePriceToXY(0, 0, 0, price, x, y);            
041.             x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290));
042.             ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0));
044.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x);
045.             ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y);
046.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_XDISTANCE, x + 10);
047.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_YDISTANCE, y - 8);
048.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 80);
049.             ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y);
050.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30);
051.             ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y);
052.         }
053. //+------------------------------------------------------------------+
054. inline void CreateLinePrice(void)
055.         {
056.             string szObj;
057.             
058.             CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault));
059.             ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color);
060.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
061.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT);
062.             ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
063.             ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr);
064.             macro_LineInFocus(false);
065.         }
066. //+------------------------------------------------------------------+
067. inline void CreateBoxInfo(const bool bMove)
068.         {
069.             string szObj;
070.             const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0};
071.             
072.             CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
073.             ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings");
074.             ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c));
075.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? m_Info._color : (m_Info.bIsBuy ? clrForestGreen : clrFireBrick)));
076.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15));
077.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
078.         }
079. //+------------------------------------------------------------------+
080. inline void CreateObjectInfoText(void)
081.         {
082.             string szObj;
083.             
084.             CreateObjectGraphics(szObj = def_NameObjLabel, OBJ_EDIT, clrNONE, (EnumPriority)(ePriorityDefault));
085.             ObjectSetString(0, szObj, OBJPROP_FONT, "Lucida Console");
086.             ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 10);
087.             ObjectSetInteger(0, szObj, OBJPROP_COLOR, clrBlack);
088.             ObjectSetInteger(0, szObj, OBJPROP_BORDER_COLOR, m_Info._color);
089.             ObjectSetInteger(0, szObj, OBJPROP_ALIGN, ALIGN_CENTER);
090.             ObjectSetInteger(0, szObj, OBJPROP_READONLY, true);
091.             ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 17);
092.             ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 60);
093.         }
094. //+------------------------------------------------------------------+
095. inline void CreateButtonClose(void)
096.         {
097.             string szObj;
098.             
099.             CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault));
100.             ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close);
101.             ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER);
102.         }
103. //+------------------------------------------------------------------+
104.     public    :
105. //+------------------------------------------------------------------+
106.         C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, char digits, string szDescr = "\n", const bool IsBuy = true)
107.             :C_Mouse(0, "")
108.         {        
109.             ZeroMemory(m_Info);
110.             m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev));
111.             m_Info._color = _color;
112.             m_Info.szDescr = szDescr;
113.             m_Info.bIsBuy = IsBuy;
114.             m_Info.digits = digits;
115.         }
116. //+------------------------------------------------------------------+
117.         ~C_ElementsTrade()
118.         {
119.             ObjectsDeleteAll(0, m_Info.szPrefixName);
120.         }
121. //+------------------------------------------------------------------+
122. inline void UpdatePrice(const double open, const double price)
123.         {
124.             if (price > 0)
125.             {
126.                 CreateLinePrice();
127.                 CreateButtonClose();
128.             }else
129.                 ObjectsDeleteAll(0, m_Info.szPrefixName);
130.             CreateBoxInfo(m_Info.ev != evMsgClosePositionEA);
131.             if (m_Info.ev == evMsgClosePositionEA)
132.                 CreateObjectInfoText();
133.             UpdateViewPort(m_Info.price = (price > 0 ? price : open));
134.         }
135. //+------------------------------------------------------------------+
136.         void ViewValue(const double profit)
137.         {
138.             ObjectSetString(0, def_NameObjLabel, OBJPROP_TEXT, StringFormat("%." + (string)m_Info.digits + "f", (profit < 0 ? -(profit) : profit)));
139.             ObjectSetInteger(0, def_NameObjLabel, OBJPROP_BGCOLOR, (profit >= 0 ? clrPaleGreen : clrCoral));
140.             ChartRedraw();
141.         }
142. //+------------------------------------------------------------------+
143.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
144.         {
145.             string sz0;
146.             long _lparam = lparam;
147.             double _dparam = dparam;
148.             
149.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
150.             switch (id)
151.             {
152.                 case (CHARTEVENT_KEYDOWN):
153.                     if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break;
154.                     _lparam = (long) m_Info.ticket;
155.                     _dparam = 0;
156.                 case CHARTEVENT_CUSTOM + evMsgSetFocus:
157.                     macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev));
158.                     EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, "");
159.                     m_Info.bClick = false;
160.                 case CHARTEVENT_CHART_CHANGE:
161.                     UpdateViewPort(m_Info.price);
162.                     break;
163.                 case CHARTEVENT_OBJECT_CLICK:
164.                     sz0 = GetPositionsMouse().szObjNameClick;
165.                     if (m_Info.bClick) switch (m_Info.ev)
166.                     {
167.                         case evMsgClosePositionEA:
168.                             if (sz0 == def_NameBtnClose)
169.                                 EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, "");
170.                             break;
171.                         case evMsgCloseTakeProfit:
172.                             if (sz0 == def_NameBtnClose)
173.                                 EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL));
174.                             else if (sz0 == def_NameBtnMove)
175.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, "");
176.                             break;
177.                         case evMsgCloseStopLoss:
178.                             if (sz0 == def_NameBtnClose)
179.                                 EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL));
180.                             else if (sz0 == def_NameBtnMove)
181.                                 EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, "");
182.                             break;
183.                     }
184.                     m_Info.bClick = false;
185.                     break;
186.                 case CHARTEVENT_MOUSE_MOVE:
187.                     m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick);
188.                     if (m_Info.weight > 1)
189.                     {
190.                         UpdateViewPort(GetPositionsMouse().Position.Price);
191.                         if (m_Info.bClick)
192.                         {
193.                             switch (m_Info.ev)
194.                             {
195.                                 case evMsgCloseTakeProfit:
196.                                     EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
197.                                     break;
198.                                 case evMsgCloseStopLoss:
199.                                     EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL));
200.                                     break;
201.                             }
202.                             EventChartCustom(0, evMsgSetFocus, 0, 0, "");
203.                         }
204.                     }
205.                     break;
206.             }
207.         }
208. //+------------------------------------------------------------------+
209. };
210. //+------------------------------------------------------------------+
211. #undef macro_LineInFocus
212. //+------------------------------------------------------------------+
213. #undef def_Btn_Close
214. #undef def_PathBtns
215. //+------------------------------------------------------------------+
216. #undef def_NameObjLabel
217. #undef def_NameInfoDirect
218. #undef def_NameBtnMove
219. #undef def_NameBtnClose
220. #undef def_NameHLine
221. //+------------------------------------------------------------------+

C_ElementsTrade

Primero revisaremos la clase C_ElementsTrade; después modificaremos los detalles necesarios del indicador. Como gran parte del código no ha cambiado, nos centraremos en las novedades. La primera aparece en la definición de la línea ocho. Después se incorpora la variable de la línea 32, cuya finalidad veremos enseguida.

Al añadir la etiqueta de beneficio o pérdida, debemos recalcular la disposición de las líneas y etiquetas gráficas. Revisa los cambios del procedimiento UpdateViewPort, definido en la línea 36. No los detallaré porque son sencillos, pero conviene tenerlos presentes para localizar correctamente cada elemento.

La línea 80 incorpora un procedimiento que crea la etiqueta gráfica y muestra en ella el resultado calculado por el indicador. Como este patrón ya ha aparecido varias veces en la serie, no repetiremos la explicación. Sí conviene revisar el constructor de la clase, definido en la línea 106. Ahora recibe el número de cifras decimales que mostrará la etiqueta. Con ese número de decimales, la etiqueta representa correctamente la variación del precio, expresada como cantidad de ticks o de puntos, que corresponde al beneficio o la pérdida. Muy bien, ahora también tenemos un nuevo procedimiento.

La línea 136 define el nuevo procedimiento público que utilizará el indicador. Aunque solo contiene tres instrucciones, conviene precisar cómo formatea la diferencia en puntos y cómo actualiza la etiqueta gráfica. El procedimiento recibe un único parámetro: la cantidad de puntos que separa el precio de apertura de la mejor cotización disponible para cerrar la posición.

En la línea 138 se llama a la función StringFormat de MQL5. Su sintaxis puede parecer peculiar: formatea como número positivo la cantidad de puntos que separa el precio de apertura del mejor precio de cierre y utiliza el número de decimales configurado para el símbolo. No debes confundir esa diferencia de precios con un importe financiero. Para entender la diferencia, conviene conocer el incremento mínimo de cotización de cada símbolo: en algunos es de 5 unidades de precio; en otros, de 0.01; y en otros, de 0.00001.

Aunque al principio parezca complicado, eso es precisamente lo que resuelve StringFormat: adapta automáticamente el número de decimales al incremento mínimo de cotización del símbolo. Mostrar más cifras decimales de las necesarias dificultaría la lectura. Podríamos fijar cinco decimales, pero esa decisión no tendría sentido para un símbolo que solo requiere dos o ninguno, como ocurre con el Índice Futuro de B3 (Bolsa de Brasil).

El formato se construye de la siguiente manera. Primero abrimos comillas dobles, escribimos el signo de porcentaje y añadimos el carácter punto antes de cerrar las comillas. IMPORTANTE: NO DEBE HABER NINGÚN ESPACIO ENTRE ESE CARÁCTER Y LAS COMILLAS DOBLES. A continuación indicamos el número de decimales almacenado en digits. Después abrimos de nuevo las comillas y escribimos una f, que identifica un número de punto flotante. TAMPOCO DEBE HABER NINGÚN ESPACIO ENTRE LA COMILLA INICIAL Y LA f. Tras la f podemos añadir los caracteres literales que deban acompañar al número formateado, siempre que la cadena termine con comillas dobles. Como la cadena de formato se construye durante la ejecución, veamos algunos ejemplos.

Supongamos que el símbolo utiliza dos cifras decimales. Cuando se ejecute la línea 138, la concatenación de sus fragmentos producirá en RUN-TIME la cadena de formato "%.2f". StringFormat sabrá entonces que debe mostrar dos decimales. El mismo código funciona, sin recompilarlo, con un símbolo que requiera cinco decimales; en ese caso, la variable digits determinará que la cadena resultante sea "%.5f".

Aunque parezca complejo, el mecanismo es sencillo y ofrece un resultado útil. La línea 139 indica de inmediato si el resultado es positivo o negativo. El número se muestra siempre en positivo, pero el color de fondo indica si la posición produciría una pérdida o un posible beneficio. Puede sorprenderte comprobar que, salvo en FOREX cuando BID y ASK coinciden, una operación bursátil comienza siempre con pérdidas: entrar y salir inmediatamente a mercado implica perder dinero. No es una posibilidad, sino una REALIDAD.

La línea 140 fuerza la actualización inmediata del gráfico. Las actualizaciones de los elementos gráficos suelen entrar en una cola de ejecución y a veces tardan unos instantes en reflejarse en el gráfico. Esta llamada evita esa demora. Fuera de los cambios descritos, el resto del código permanece igual. Veamos ahora las modificaciones del indicador, cuyo código completo aparece a continuación.

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

Código fuente del Indicador de posición

Los cambios restantes se limitan a la declaración de una variable y a su inicialización. En la línea 25 se añade una variable que pasamos al constructor de C_ElementsTrade. La variable declarada en la línea 25 se inicializa en la línea 42 con el número de decimales definido para el símbolo de la posición. El nombre del símbolo se obtiene en la línea 41, después de localizar la posición en la línea 35.

Como era de esperar, los constructores de las líneas 88 a 90 también cambian ligeramente. La principal novedad, sin embargo, está en OnCalculate: con respecto a la versión inicial del artículo, solo se ha añadido la línea 69. Podemos eliminar la línea 68, pues únicamente muestra el resultado con fines de comprobación. La línea 69 repite exactamente el mismo cálculo.

Así, la etiqueta mostrará la diferencia entre el precio de apertura y el mejor precio de salida, ya sea mediante una ejecución contra el BID o contra el ASK. Insisto: LO QUE VERÁS NO ES UN IMPORTE FINANCIERO, SINO LA CANTIDAD DE PUNTOS QUE SEPARA EL PRECIO DE APERTURA DEL MEJOR PRECIO DE CIERRE. No interpretes esa cifra como una ganancia o pérdida monetaria. Para obtener el importe financiero se requiere otro cálculo, que abordaremos más adelante.

La siguiente animación muestra el resultado sin necesidad de probarlo localmente.

Observa que la línea de la última operación, o precio de cierre, puede cambiar sin alterar el resultado de una salida a mercado. Esto ocurre porque la cotización BID o ASK permanece igual aunque varíe el precio de cierre.


Consideraciones finales

En este artículo mostré cómo implementar, sin mucho esfuerzo, una etiqueta gráfica que indica si una posición genera pérdidas o beneficios. El procedimiento es sencillo y eficaz. Muchos operadores principiantes no comprenden con precisión el efecto del spread y, por ello, pueden sufrir pérdidas frecuentes. Incluso sin grandes conocimientos, este indicador te permitirá reconocer con facilidad cuándo conviene cerrar una posición. Así evitarás resultados inesperados, Este indicador muestra con mayor claridad cuándo conviene cerrar una posición y reduce el riesgo de obtener un resultado distinto del esperado. El cálculo incorpora el SPREAD del cierre, a diferencia de la simple lectura de POSITION_PROFIT.

El archivo adjunto contiene todas las piezas necesarias para probar la aplicación localmente. Te recomiendo utilizar una cuenta DEMO. No ejecutes el sistema en una cuenta REAL hasta conocer bien su funcionamiento. El material está diseñado con fines didácticos, no como una aplicación definitiva y libre de fallos.

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 la interacción con el servicio de repetición/simulador. (Es necesario usar Mouse Study para la interacción)
Indicators\Mouse Study.mq5Permite la interacción entre los controles gráficos y el usuario (Necesario tanto para operar el sistema de repetición/simulador como en el mercado real)
Indicators\Order Indicator.mq5Responsable de indicar órdenes de mercado, permitiendo interactuar con ellas y controlarlas
Indicators\Position View.mq5Responsable de indicar posiciones de mercado, permitiendo interactuar con ellas y controlarlas
Services\Market Replay.mq5Crea y mantiene el servicio de repetición y simulación de mercado (Archivo principal de todo el sistema)

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

Archivos adjuntos |
Anexo.zip (779.24 KB)
Del básico al intermedio: Clases (I) Del básico al intermedio: Clases (I)
En este artículo explicamos qué es una clase y por qué surgió este concepto. Aunque el tema es interesante, aquí nos centraremos en los principios que rigen la programación en MQL5. Este artículo es solo una introducción.
Características del Wizard MQL5 que debe conocer (Parte 70): Uso de patrones del SAR y el RVI con una red de kernel exponencial Características del Wizard MQL5 que debe conocer (Parte 70): Uso de patrones del SAR y el RVI con una red de kernel exponencial
Retomamos nuestro último artículo, donde presentamos el par de indicadores SAR y RVI, y ahora analizamos cómo se podría ampliar este par de indicadores con el aprendizaje automático. SAR y RVI forman una combinación complementaria de tendencia e impulso. Nuestro enfoque de aprendizaje automático usa una CNN 1D con escalado exponencial en el tamaño de los kernels y en el número de canales, al tiempo que ajusta las previsiones de este par de indicadores. Como siempre, esto se realiza en un archivo de clase de señal personalizado que funciona con el asistente MQL5 para ensamblar un Asesor Experto.
Simulación de mercado: Position View (XIV) Simulación de mercado: Position View (XIV)
Ahora implementaremos esta solución porque MQL5 sigue el mismo principio que la programación basada en eventos. Los desarrolladores utilizan este modelo con frecuencia al crear DLL. Sé que, al principio, el funcionamiento basado en eventos parecerá confuso y carente de lógica. Pero, en este artículo, presentaré los principios de la programación basada en eventos con mayor claridad para que tú, que estás empezando, comprendas adecuadamente su funcionamiento. Entender lo que comenzaré a explicar en este artículo te ayudará en tu vida como programador.
Algoritmo de Aprendizaje Competitivo — Competitive Learning Algorithm (CLA) Algoritmo de Aprendizaje Competitivo — Competitive Learning Algorithm (CLA)
El artículo presenta el Algoritmo de Aprendizaje Competitivo (CLA), un nuevo método de optimización metaheurística basado en la modelización del proceso educativo. El algoritmo organiza la población de soluciones en clases con estudiantes y profesores, donde los agentes aprenden a través de tres mecanismos: siguiendo a los mejores de la clase, usando la experiencia personal y compartiendo conocimientos entre clases.