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

Simulación de mercado: Position View (III)

MetaTrader 5Probador |
21 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 (II), creamos algo muy simple, cuyo objetivo era mostrar en el gráfico dónde se encuentran las líneas de precios en una posición abierta. Aunque ese indicador puede mostrarnos esa información, todavía estamos lejos de tener algo realmente práctico, ya que aún tenemos algunos pequeños problemas por resolver. Pero no es nada complicado, sino solo algo que tú, estimado lector y entusiasta, necesitas entender antes de que podamos avanzar un poco más en la implementación del código.

Para simplificar un poco las cosas y hacerlas más fáciles de asimilar, vamos a ver esto en un tema dedicado solo a esa tarea. Así que vayamos a ello, sin más demora.


Entendiendo la propiedad ZOrder

No sé si tú, estimado lector, ya has notado que en los objetos de MQL5 tenemos un valor que, en general, se usa o se define muy poco en algún código. Yo mismo revisé diversos códigos diferentes y no encontré a nadie que realmente defina esa propiedad de los objetos. La propiedad a la que me refiero es ZOrder. Si vienes siguiendo esta serie, debes haber notado que, desde hace ya bastante tiempo, esta propiedad se define en la clase C_Terminal, al crear los objetos mediante la llamada CreateObjectGraphics, que es un procedimiento de esa clase.

Y, en estos últimos artículos, he mencionado que, en algunos momentos, necesitamos definir un valor para esta propiedad. ¿Pero por qué? El motivo es que muchos de los códigos que agregan objetos al gráfico simplemente no usan, o mejor dicho, no definen un valor para esa propiedad. Bien, no estoy aquí para decir qué debe o no debe hacer cada programador, ni cómo debe o no debe escribir su código. Estoy aquí para mostrarte, estimado lector e interesado en comprender realmente cómo funcionan las cosas, lo que ocurre entre bastidores.

Sin entender lo que voy a explicar, o sin comprender adecuadamente determinados conceptos sobre el uso de MetaTrader 5, o incluso de la programación MQL5, tú, sin ninguna duda, acabarás teniendo una pésima experiencia al usar la plataforma. Y uno de esos conceptos involucra justamente la propiedad ZOrder.

Para experimentar realmente las cosas de la forma más simple posible, para que entiendas de qué estoy hablando, vamos a usar el siguiente código, que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. input char user00 = 0;      //ZOrder to Chart #01
08. input char user01 = 0;      //ZOrder to Chart #02
09. //+------------------------------------------------------------------+
10. void ChartOfTest(string sz1, int x, int y, int zOrder)
11. {
12.     long id = ChartID();
13.     
14.     ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0);
15.     ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x);
16.     ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y);
17.     ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder);
18.     ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true);
19. }
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.     ChartOfTest("Chart #01", 160, 140, user00);
24.     ChartOfTest("Chart #02", 200, 200, user01);
25.     
26.     return INIT_SUCCEEDED;
27. }
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
30. {
31.     return rates_total;
32. }
33. //+------------------------------------------------------------------+
34. void OnDeinit(const int reason)
35. {
36.     ObjectsDeleteAll(ChartID(), "Chart #");
37. }
38. //+------------------------------------------------------------------+

Código fuente del indicador

Este código, muy simple, hace exactamente lo que necesitamos para entender cómo la propiedad ZOrder puede afectar, y afectará, tu experiencia de uso en MetaTrader 5. Observa que, en las líneas 23 y 24, llamaremos al procedimiento responsable de crear dos objetos y colocarlos en la pantalla del gráfico que esté abierto en ese momento. Los objetos que se crearán son OBJ_CHART, como puedes ver en la línea 14. Para facilitarte las cosas, estimado lector, en las líneas siete y ocho permitimos dos puntos de control. Su finalidad es evitar que tengas que ajustar los valores de ZOrder de los objetos en todo momento.

Por defecto, los valores siempre son cero. Por esta razón, los dejaremos así por ahora. El resultado de la ejecución puede verse en la siguiente imagen.

Algo simple y evidente, como cabría esperar. Pero, ¿y si cambiamos el marco temporal del gráfico? ¿Qué ocurrirá en esa ventana, donde tenemos un indicador cuyo objetivo es crear un fondo de pantalla para el gráfico? ¿Nos quedaremos sin poder acceder a los objetos OBJ_CHART? Bien, esto puede verse en la siguiente animación.

Observa que, aunque el fondo de pantalla, es decir, un objeto del tipo OBJ_BITMAPLABEL, está por encima de los objetos OBJ_CHART, esto normalmente podría causar problemas. El fondo de los OBJ_CHART ha dejado de ser negro y ahora muestra el contenido de la región en la que están sobre el OBJ_BITMAPLABEL. Sin embargo, como en la línea 17 estamos definiendo un valor cero para ZOrder, y el valor de ZOrder del objeto donde está el fondo de pantalla es menor, en este caso -1, como vimos en los artículos anteriores, la respuesta al clic, o la forma de seleccionar los objetos OBJ_CHART, no se vio afectada, como puedes ver en la animación anterior. 

Sin embargo, tendrás la clara ilusión de que un objeto que esté en primer plano tendrá prioridad, como se ve en la siguiente animación.

Como acabo de decir, esto es una ilusión, ya que el objeto que realmente está en primer plano es el OBJ_BITMAPLABEL, que contiene la imagen que rellena todo el gráfico, creando así un fondo de pantalla. Pero esta ilusión se vuelve todavía más intrigante cuando modificas la prioridad de los objetos. Para hacerlo, usando el código anterior, mira la siguiente animación.

Ten en cuenta que cambié la prioridad del objeto que se encuentra al fondo, es decir, el CHART #01. Así, como tiene una prioridad más alta, obtenemos el resultado que se ve en la animación. Si no lo has comprendido, pruébalo en tu propia estación de MetaTrader 5. Notarás que, incluso haciendo clic en una región que pertenece al CHART #02, pero que también pertenece al CHART #01, se seleccionará el CHART #01. Solo podrás seleccionar el CHART #02 si, y solo si, haces clic en una región fuera del CHART #01, pero que pertenezca al CHART #02. Si ambos están exactamente en la misma región, el CHART #01 tendrá prioridad, aunque esté cubierto por el CHART #02.

Esto, sin duda, se volverá mucho más interesante si pruebas a colocar en los OBJ_CHART valores menores o iguales que el del objeto OBJ_BITMAPLABEL, que cumple la función de fondo de pantalla. O si colocas otro objeto, mediante el atajo que se muestra en la siguiente imagen.

Prueba a hacer esto: agrega un OBJ_CHART más, pero esta vez mediante el atajo mostrado antes, y juega con los valores del indicador, como se muestra en la animación anterior. Sin duda, empezarás a entender la importancia de usar un valor adecuado en la propiedad ZOrder. ¿Pero dónde se aplica esto en lo que estamos desarrollando en este momento? Bien, para responder a esto de la forma más adecuada y separar así los temas, vamos a verlo en un nuevo tema.


Haciendo uso del conocimiento adquirido

Muy bien, saber y comprender lo que acabo de mostrar es primordial para que puedas entender algo que se hará en el momento de la implementación. Más adelante, esto dejará de tener sentido, ya que usaremos otro enfoque. Pero, en este momento, es importante, sobre todo según cómo modifiques o ajustes el código para tu uso particular.

En el artículo anterior, creamos tres líneas: una para el precio de apertura de la posición, una para el punto de stop loss y una para el punto de take profit. Muy bien, hasta este punto no hay nada incorrecto en lo que se hizo. La línea horizontal del precio de apertura nunca deberá moverse. Esto se resolverá pronto. En cambio, las líneas horizontales de stop loss y take profit pueden moverse libremente. Y es en este punto donde tenemos un problema. El problema surge cuando ambas líneas, la de stop loss y la de take profit, quedan al mismo precio.

En ese caso, ¿qué línea deberá tener prioridad durante un intento de seleccionar una de ellas? Esto, para que reciba un determinado evento, que puede ser mover la línea o incluso eliminarla. Tú podrías decir que es la de stop loss, mientras que otro operador podría decir que sería la de take profit. ¿Quién tendría realmente la razón en esa situación?

¿Ves el problema que tenemos aquí? De cierta manera, el orden en que se creen las líneas influirá en esa decisión, si ambas tienen el mismo valor de ZOrder. Así, el simple hecho de cambiar el orden de creación ya resolvería muchos de los problemas. Sin embargo, llevemos esto a una situación aún más extrema. No es raro que algunos operadores, especialmente en cuentas HEDGING, tengan más de una posición abierta. En ese caso, puedes querer establecer un ZOrder diferente entre la línea de stop loss y la de take profit. Pero, aunque hayas hecho esto, lo que eliminaría el problema de que una línea de stop loss quede en el mismo punto que una línea de take profit, tendrías otro problema: cuando dos líneas, como por ejemplo dos de stop loss, lleguen a quedar superpuestas.

¿Notas cómo no sirve de nada cambiar ZOrder de cualquier manera? Aunque uses valores distintos para resolver un problema, siempre habrá otro por resolver. Sin embargo, según algunos operadores profesionales, que me asesoran en algunos aspectos relacionados con el desarrollo y la implementación del sistema de repetición/simulador, no existe un motivo práctico para que tengamos más de dos, tres o, como máximo, cuatro datos en el servidor. Es decir, si estamos usando una cuenta HEDGING, según ellos, no tiene sentido que tengas más de dos posiciones abiertas al mismo tiempo, porque en una cuenta NETTING no tenemos este problema. En este primer momento, pensaré solo en cuentas HEDGING. Pero esto no significa que el indicador no vaya a funcionar en cuentas NETTING.

Así pues, volvamos al código que vimos en el artículo anterior. Allí implementamos las líneas de stop loss y take profit con el mismo ZOrder. Para cambiar esto, tendremos que hacer un pequeño cambio en el código. El cambio es tan puntual que no entraré en detalles. Solo mira cómo quedará el código modificado. Esto puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #define def_SufixLinePrice   "Price"
09. #define def_SufixLineTake    "Take"
10. #define def_SufixLineStop    "Stop"
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Auxiliar\C_Terminal.mqh>
13. //+------------------------------------------------------------------+
14. input color user00 = clrRoyalBlue;          //Color Line Price
15. input color user01 = clrForestGreen;        //Color Line Take Profit
16. input color user02 = clrFireBrick;          //Color Line Stop Loss
17. //+------------------------------------------------------------------+
18. C_Terminal *Terminal;
19. struct st
20. {
21.     long       id;
22.     string     szPrefixName;
23. }glVariables;
24. //+------------------------------------------------------------------+
25. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
26. {
27.     if (price <= 0) return;
28.     (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02))));
29.     ObjectSetDouble(glVariables.id, szObjName, OBJPROP_PRICE, price);
30.     ObjectSetString(glVariables.id, szObjName, OBJPROP_TEXT, szDescription);
31.     ObjectSetString(glVariables.id, szObjName, OBJPROP_TOOLTIP, szDescription);
32.     ObjectSetInteger(glVariables.id, szObjName, OBJPROP_SELECTABLE, cor != user00);
33. }
34. //+------------------------------------------------------------------+
35. int OnInit()
36. {
37.     ZeroMemory(glVariables);
38.     Terminal = new C_Terminal();
39.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
40.     glVariables.id = (*Terminal).GetInfoTerminal().ID;
41.     glVariables.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
42.     CreateLineInfos(glVariables.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price.");
43.     CreateLineInfos(glVariables.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point.");
44.     CreateLineInfos(glVariables.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point.");
45.     
46.     return INIT_SUCCEEDED;
47. }
48. //+------------------------------------------------------------------+
49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
50. {
51.     return rates_total;
52. }
53. //+------------------------------------------------------------------+
54. void OnDeinit(const int reason)
55. {
56.     delete Terminal;
57.     if (glVariables.id > 0)
58.         ObjectsDeleteAll(glVariables.id, glVariables.szPrefixName);
59. }
60. //+------------------------------------------------------------------+

Indicador Position View

Observa que ahora la línea de stop loss tendrá prioridad sobre la línea de take profit. Esto se debe a que tiene un valor de ZOrder mayor. Entonces, en caso de que tengamos dos posiciones abiertas, y la línea de stop loss esté al mismo precio que una línea de take profit, al intentar cualquier manipulación de estas líneas, la línea de stop loss tendrá preferencia. Observa que el único cambio para que esto ocurra se hizo en la línea 28. Compara esa misma línea con la del código anterior para entender el cambio.

Sin embargo, aunque este indicador funciona, no es adecuado para usarse en cuentas HEDGING. El motivo es que siempre buscará la primera posición abierta, o la última. Y, aunque hagas algo para cambiar esto, de una forma u otra, no pasará de ser un placebo. Necesitamos implementar una solución con mejores resultados y de alcance más general. Además, puedes notar que dependemos de que los colores sean distintos para que el indicador sepa qué representa cada línea. Y este tipo de solución sirve para pruebas. Pero, para una aplicación más general, resulta completamente inadecuada. De esta forma, tenemos muchas cosas que resolver y corregir aquí.

En este punto, muchos de los que están empezando en el área de programación suelen desistir, o hacen tantos cambios que terminan sin poder gestionar el código que están creando. Así, si ya sabes programar y tienes los conceptos correctos, te pido que tengas paciencia con quienes tienen menos experiencia, ya que voy a mostrar cómo deberían empezar a pensar quienes aún están comenzando, para que así le tomen gusto al arte de programar y consigan crear sus propias soluciones.


Empezando a corregir las cosas

Cuando tú, estimado lector, te encuentres con un código como el anterior, en el que debas mejorarlo de alguna manera, no empieces cambiándolo sin criterio. Empieza separando las cosas en partes, pero manteniendo siempre el código en funcionamiento. No, y repito: NO cambies nada antes de separar realmente las cosas. Una de las formas más simples y adecuadas de hacerlo es colocar algunas partes del código en un archivo de cabecera, para enseguida empezar a modificarlo. Pero, como es mucho más simple llevar todas las partes a una clase, eso es lo que haremos. ¿Pero por qué llevar el código a una clase es más simple que separarlo en archivos de cabecera?

El motivo es simple. Si colocas el código en una clase, podrás ponerlo en un archivo de cabecera más adelante. Sin embargo, llevarlo simplemente a un archivo de cabecera no hace que sea más fácil de gestionar. Esto se debe a que en el futuro pueden surgir conflictos de nombres. No es raro que en códigos con muchos archivos existan este tipo de conflictos. En cambio, cuando llevamos las cosas a una clase, la posibilidad de conflictos disminuye bastante. No es que se eliminen por completo, como verás a medida que avances en programación. Sin embargo, resolver esos conflictos será considerablemente más simple y rápido. Sabiendo esto, empecemos llevando las cosas a una nueva clase dentro del sistema de repetición/simulador.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #define def_SufixLinePrice   "Price"
09. #define def_SufixLineTake    "Take"
10. #define def_SufixLineStop    "Stop"
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Auxiliar\C_Terminal.mqh>
13. //+------------------------------------------------------------------+
14. input color user00 = clrRoyalBlue;          //Color Line Price
15. input color user01 = clrForestGreen;        //Color Line Take Profit
16. input color user02 = clrFireBrick;          //Color Line Stop Loss
17. //+------------------------------------------------------------------+
18. class C_IndicatorPosition
19. {
20.     private    :
21.         struct st00
22.         {
23.             long      id;
24.             string    szPrefixName;
25.         }m_Infos;
26. //+------------------------------------------------------------------+
27.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
28.         {
29.             if (price <= 0) return;
30.             (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02))));
31.             ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price);
32.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription);
33.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription);
34.             ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00);
35.         }
36. //+------------------------------------------------------------------+
37.     public    :
38. //+------------------------------------------------------------------+
39.         C_IndicatorPosition(const long Id)
40.         {
41.             ZeroMemory(m_Infos);
42.             m_Infos.id = Id;
43.             m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
44.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price.");
45.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point.");
46.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point.");
47.         }
48. //+------------------------------------------------------------------+
49.         ~C_IndicatorPosition()
50.         {
51.             if (m_Infos.id > 0)
52.                 ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName);
53.         }
54. //+------------------------------------------------------------------+
55. };
56. //+------------------------------------------------------------------+
57. C_Terminal *Terminal;
58. C_IndicatorPosition *Positions;
59. //+------------------------------------------------------------------+*/
60. int OnInit()
61. {
62.     Terminal = new C_Terminal();
63.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
64.     Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID);
65.     
66.     return INIT_SUCCEEDED;
67. }
68. //+------------------------------------------------------------------+
69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
70. {
71.     return rates_total;
72. }
73. //+------------------------------------------------------------------+
74. void OnDeinit(const int reason)
75. {
76.     delete Terminal;
77.     delete Positions;
78. }
79. //+------------------------------------------------------------------+

Código fuente de Position View

En este código que se muestra a continuación, puedes imaginar que le hemos añadido complejidad. Pero, en realidad, todo lo que hicimos fue colocar dentro de una clase el código que antes estaba fuera de ella. Así, al contrario de lo que pueda parecer, no añadimos complejidad, sino que mejoramos la forma en que podemos implementar las cosas, ya que el mismo código que existía antes ahora cuenta con una mejor protección y nos permite dividir mejor las tareas. Digo que hay una mayor protección porque la función CreateLineInfos ahora es privada. Es decir, el autor de llamada o el usuario no necesita saber cómo se creará realmente el indicador. Solo pide que se cree el indicador. La forma de hacerlo puede cambiar bastante con el tiempo, pero para el usuario siempre será la misma.

Sin embargo, hacer lo que acabo de mostrar no resuelve ninguno de los problemas que tenemos. Solo facilita que podamos implementar el código de una mejor manera. Observa que todo el código sigue igual que antes y que su funcionamiento no ha cambiado en nada. Esto es lo primero que siempre debes garantizar. No cambies el código hasta que esté funcionando exactamente como funcionaba antes de ponerlo dentro de una clase. Después de eso, lo que se haga dependerá de lo que necesites o quieras hacer primero. Pero, como la clase ya fue creada, podemos colocarla de una vez en un archivo de cabecera. Así no recargamos innecesariamente el código principal. Entonces, crearemos un archivo con el mismo nombre de la clase que se colocará en él. Esta es una buena práctica de programación. Sin embargo, no es obligatorio hacerlo así. Con esto, el nuevo código principal puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Auxiliar\C_Terminal.mqh>
09. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
10. //+------------------------------------------------------------------+
11. input color user00 = clrRoyalBlue;          //Color Line Price
12. input color user01 = clrForestGreen;        //Color Line Take Profit
13. input color user02 = clrFireBrick;          //Color Line Stop Loss
14. //+------------------------------------------------------------------+
15. C_Terminal *Terminal;
16. C_IndicatorPosition *Positions;
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     Terminal = new C_Terminal();
21.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
22.     Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID);
23.     
24.     return INIT_SUCCEEDED;
25. }
26. //+------------------------------------------------------------------+
27. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
28. {
29.     return rates_total;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     delete Terminal;
35.     delete Positions;
36. }
37. //+------------------------------------------------------------------+

Código fuente de Position View

¿Y dónde fue a parar el código de la clase? Bien, ahora se encuentra en el archivo cuya ubicación se indica en la línea nueve del código anterior. Observa que la clase se llevó con ella algunos datos que antes estaban aquí, en el código principal. Esto se debe a que, como se dijo antes, el usuario no necesita saber cómo funcionan las cosas. Solo necesita saber qué debe invocar y qué parámetros debe pasar al código de la clase. La forma en que la clase procesará los datos no le interesa al autor de llamada. Entonces, mira el código del archivo de cabecera C_IndicatorPosition, que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixLinePrice   "Price"
05. #define def_SufixLineTake    "Take"
06. #define def_SufixLineStop    "Stop"
07. //+------------------------------------------------------------------+
08. class C_IndicatorPosition
09. {
10.     private    :
11.         struct st00
12.         {
13.             long      id;
14.             string    szPrefixName;
15.         }m_Infos;
16. //+------------------------------------------------------------------+
17.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
18.         {
19.             if (price <= 0) return;
20.             (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02))));
21.             ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price);
22.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription);
23.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription);
24.             ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00);
25.         }
26. //+------------------------------------------------------------------+
27.     public    :
28. //+------------------------------------------------------------------+
29.         C_IndicatorPosition(const long Id)
30.         {
31.             ZeroMemory(m_Infos);
32.             m_Infos.id = Id;
33.             m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
34.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price.");
35.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point.");
36.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point.");
37.         }
38. //+------------------------------------------------------------------+
39.         ~C_IndicatorPosition()
40.         {
41.             if (m_Infos.id > 0)
42.                 ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName);
43.         }
44. //+------------------------------------------------------------------+
45. };
46. //+------------------------------------------------------------------+
47. #undef def_SufixLinePrice
48. #undef def_SufixLineTake
49. #undef def_SufixLineStop
50. //+------------------------------------------------------------------+

Código fuente de C_IndicatorPosition.mqh

Observa que el código sigue igual. Solo se añadieron tres líneas nuevas al final del archivo. Su objetivo es eliminar las definiciones que solo tiene sentido mantener en este archivo de cabecera. Es decir, estamos usando el principio de mínimo privilegio. O, para que tú, estimado lector, lo entiendas: nadie necesita saber lo que no le corresponde. Esto evita que la información se filtre sin que lo notes. Además, por supuesto, es una buena práctica de programación, ya que otros códigos pueden querer usar los mismos nombres que definiste aquí. Y, si no los eliminas aquí, tendrás que hacerlo en otro lugar, lo que causa muchos inconvenientes cuando hay que mejorar un código muy grande, porque aparecen errores que no tienen ningún sentido debido a conflictos en las definiciones.

Bien. Pero, si ahora miras solo el código del archivo de cabecera, notarás que allí se declaran y usan valores que dependen del código principal. Esto es un problema, ya que, si el código principal cambia, este archivo de cabecera también tendrá que cambiar. Es decir, un trabajo enorme. Por lo tanto, tenemos que corregir esto. Una buena forma de hacerlo es pedir que el código principal informe los valores de manera independiente. Así, el nuevo archivo de cabecera se muestra a continuación, con los cambios ya implementados.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixLinePrice   "Price"
05. #define def_SufixLineTake    "Take"
06. #define def_SufixLineStop    "Stop"
07. //+------------------------------------------------------------------+
08. class C_IndicatorPosition
09. {
10.     private    :
11.         struct st00
12.         {
13.             long      id;
14.             string    szPrefixName;
15.             color     corPrice, corTake, corStop;
16.         }m_Infos;
17. //+------------------------------------------------------------------+
18.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
19.         {
20.             if (price <= 0) return;
21.             (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
22.             ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price);
23.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription);
24.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription);
25.             ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
26.         }
27. //+------------------------------------------------------------------+
28.     public    :
29. //+------------------------------------------------------------------+
30.         C_IndicatorPosition(const long Id, color corPrice, color corTake, color corStop)
31.         {
32.             ZeroMemory(m_Infos);
33.             m_Infos.id = Id;
34.             m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
35.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice = corPrice, "Position opening price.");
36.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake = corTake, "Take Profit point.");
37.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop = corStop, "Stop Loss point.");
38.         }
39. //+------------------------------------------------------------------+
40.         ~C_IndicatorPosition()
41.         {
42.             if (m_Infos.id > 0)
43.                 ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName);
44.         }
45. //+------------------------------------------------------------------+
46. };
47. //+------------------------------------------------------------------+
48. #undef def_SufixLinePrice
49. #undef def_SufixLineTake
50. #undef def_SufixLineStop
51. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

Observa que ahora el constructor tendrá que recibir tres argumentos nuevos. Estos informan los colores de cada una de las líneas. Así, el código principal deberá modificarse en el siguiente punto, que se muestra en el fragmento a continuación.

17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     Terminal = new C_Terminal();
21.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
22.     Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID, user00, user01, user02);
23.     
24.     return INIT_SUCCEEDED;
25. }
26. //+------------------------------------------------------------------+

Fragmento del Indicador

Así de simple. Ahora ya tenemos un código casi totalmente independiente. Esto se debe a que todavía dependemos de una lectura, por parte del código principal, sobre la existencia o no de alguna posición. Esto se hace en la línea 21, en el fragmento anterior. Entonces, vamos a corregirlo, porque queremos que el indicador funcione en cuentas HEDGING. Ya que, en ellas, podremos tener más de una posición abierta al mismo tiempo y en el mismo símbolo. Pero, para resolver esto, tendremos que cambiar el código de una manera un poco más profunda. Sin embargo, como dividimos las cosas, hacer este cambio se vuelve mucho más simple y agradable. Así pues, el nuevo código del indicador puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #define def_ShortName "Position View"
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
11. //+------------------------------------------------------------------+
12. input color user00 = clrRoyalBlue;          //Color Line Price
13. input color user01 = clrForestGreen;        //Color Line Take Profit
14. input color user02 = clrFireBrick;          //Color Line Stop Loss
15. //+------------------------------------------------------------------+
16. C_IndicatorPosition *Positions;
17. //+------------------------------------------------------------------+*/
18. int OnInit()
19. {
20.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
21.     Positions = new C_IndicatorPosition(user00, user01, user02);    
22.     if (!Positions.CheckCatch())
23.     {
24.         ChartIndicatorDelete(ChartID(), 0, def_ShortName);
25.         return INIT_FAILED;
26.     }
27.     
28.     return INIT_SUCCEEDED;
29. }
30. //+------------------------------------------------------------------+
31. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
32. {
33.     return rates_total;
34. }
35. //+------------------------------------------------------------------+
36. void OnDeinit(const int reason)
37. {
38.     delete Positions;
39. }
40. //+------------------------------------------------------------------+

Código fuente de Position View

Observa que tenemos algunos cambios bastante simples en el código del procedimiento OnInit. Básicamente, ahora estamos trabajando de tal forma que, si el indicador falla, se eliminará del gráfico. Esto se consigue mediante las líneas 20 y 24. La línea 24 es precisamente la que eliminará el indicador, en caso de que no pueda permanecer en el gráfico. Pero ¿qué tipo de condiciones impiden que el indicador permanezca en el gráfico? Bien, en este momento, solo una condición. Y, para entender esto, vamos a ver el código que se llama en esta línea 22. Así, pasamos al nuevo código de la clase, que puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixLinePrice   "Price"
05. #define def_SufixLineTake    "Take"
06. #define def_SufixLineStop    "Stop"
07. //+------------------------------------------------------------------+
08. #include "..\Auxiliar\C_Terminal.mqh"
09. //+------------------------------------------------------------------+
10. class C_IndicatorPosition : private C_Terminal
11. {
12.     private    :
13.         struct st00
14.         {
15.             ulong     ticket;
16.             string    szPrefixName;
17.             color     corPrice, corTake, corStop;
18.         }m_Infos;
19. //+------------------------------------------------------------------+
20.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
21.         {
22.             if (price <= 0) return;
23.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
24.             ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price);
25.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription);
26.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription);
27.             ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
28.         }
29. //+------------------------------------------------------------------+
30.     public    :
31. //+------------------------------------------------------------------+
32.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
33.             :C_Terminal()
34.         {
35.             ZeroMemory(m_Infos);
36.             m_Infos.corPrice = corPrice;
37.             m_Infos.corTake  = corTake;
38.             m_Infos.corStop  = corStop;
39.         }
40. //+------------------------------------------------------------------+
41.         ~C_IndicatorPosition()
42.         {
43.             if (m_Infos.ticket != 0)
44.                 ObjectsDeleteAll(GetInfoTerminal().ID, m_Infos.szPrefixName);
45.         }
46. //+------------------------------------------------------------------+
47.         bool CheckCatch(void)
48.         {
49.             for (int count = PositionsTotal() - 1; count >= 0; count--, m_Infos.ticket = 0)
50.                 if ((m_Infos.ticket = PositionGetTicket(count)) > 0)
51.                     if (PositionGetString(POSITION_SYMBOL) == GetInfoTerminal().szSymbol)
52.                     {
53.                         m_Infos.szPrefixName = IntegerToString(m_Infos.ticket);
54.                         if (ObjectFind(GetInfoTerminal().ID, m_Infos.szPrefixName + def_SufixLinePrice) < 0)
55.                             break;
56.                     }
57.             if (m_Infos.ticket == 0) return false;
58.             IndicatorSetString(INDICATOR_SHORTNAME, IntegerToString(m_Infos.ticket));
59.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price.");
60.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point.");
61.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point.");
62.             
63.             return true;
64.         }
65. //+------------------------------------------------------------------+
66. };
67. //+------------------------------------------------------------------+
68. #undef def_SufixLinePrice
69. #undef def_SufixLineTake
70. #undef def_SufixLineStop
71. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

Observa que el código está creciendo y volviéndose cada vez más complejo. Sin embargo, voy poco a poco para que tú, estimado lector, puedas seguir realmente lo que estoy haciendo. Esto se debe a que entender este código a fondo será muy importante en el futuro. Así que no tengas prisa. Sigue con calma y atención los cambios que se van produciendo, porque los mostraré y explicaré poco a poco, para que la comprensión sea la mejor posible.

Observa que, desde la última vez que vimos este código, no tenía ninguna directiva include. Ahora sí. Y esta es precisamente la que hace posible que el código principal requiera escribir menos código, además, por supuesto, de ayudar en otros puntos de la codificación. En la línea 10, incluimos, mediante herencia, la clase C_Terminal en la clase C_IndicatorPosition. Esta herencia permite que usemos, al menos por ahora, los métodos públicos de la clase C_Terminal directamente en la clase C_IndicatorPosition. Es como si esta clase C_IndicatorPosition tuviera más funciones de las que existen en MQL5. Pero esto es solo una ilusión, ya que las funciones utilizadas están en realidad en la clase C_Terminal.

Sin embargo, ten en cuenta que no podrás usar esas mismas funciones de la clase C_Terminal fuera de la clase C_IndicatorPosition. Esto se debe a que la herencia es privada, lo que impide que se acceda externamente a los métodos de la clase C_Terminal a través de la clase C_IndicatorPosition.

Pero, como estamos heredando la clase C_Terminal, tenemos que inicializarla antes de usar la clase C_IndicatorPosition. Esto se hace en la línea 33. Ahora, observa que, en el constructor, eliminamos el código anterior que estaba allí y dejamos solo el código que inicializará las variables internas de la clase. Bien. Ahora pasemos a la línea 47, donde tenemos una función responsable de comprobar si el indicador debe o no permanecer en el gráfico durante la inicialización.

En el bucle de la línea 49 es donde, a partir de este momento, pasamos a contemplar las posiciones en cuentas HEDGING. Hasta entonces, solo cubríamos cuentas NETTING. Observa que recorreremos la lista de posiciones abiertas en busca de una posición que todavía no haya sido capturada. ¿Y cómo hacemos esto? Bien, en cada iteración del bucle haremos una comprobación en la línea 50. Si esta comprobación es positiva, haremos una nueva, en la línea 51. Ahora, atención. Si esta segunda comprobación, en la línea 51, es positiva, generaremos en la línea 53 un prefijo para el nombre de los objetos que se crearán. Hasta este punto, todo sigue igual que antes. Claro, con la excepción de que ahora podremos observar más de una posición abierta en un mismo símbolo, que es justamente lo que ocurre en una cuenta HEDGING.

Excelente. Ahora viene el gran detalle. ¿Cómo sabrá el indicador si la posición ya fue analizada o no? Presta atención a esto. Cada indicador de posición presente en el gráfico observará, o mejor dicho, pertenecerá, a una única posición. Puede haber varias posiciones abiertas. Sin embargo, cada indicador pertenecerá solamente a una posición. Un mismo indicador no, y repito, NO observará más de una posición. Así, para que el indicador sepa si la posición ya está siendo analizada o no por otro indicador de posición, recorremos los objetos del gráfico. Esto se hace en la línea 54.

Si MetaTrader 5 informa que el objeto ya se encuentra en el gráfico, buscaremos una nueva posición abierta en el mismo símbolo. El bucle de la línea 49 volverá a ejecutarse. Este tipo de situación se repetirá hasta que ocurra una de estas dos condiciones. La primera es que ya no tengamos más posiciones abiertas. La segunda es que se ejecute la línea 55.

De cualquier forma, la línea 57 se ejecutará en algún momento. Si no tenemos ningún valor en la variable ticket, devolveremos un valor falso, indicando al código principal que el indicador deberá eliminarse del gráfico. Si tenemos algún valor en la variable ticket, daremos un nuevo nombre al indicador para poder acceder a él después, y crearemos las líneas como se ha visto hasta ahora. Al final, devolveremos un valor verdadero, en la línea 63. De esta manera, conseguiremos que el indicador pueda trabajar de la misma forma tanto en cuentas HEDGING como en cuentas NETTING, sin ninguna comprobación adicional.


Consideraciones finales

En este artículo, presenté los cambios necesarios para que el indicador de posiciones abiertas sea realmente funcional en cuentas HEDGING y NETTING. Es cierto, y puedes notarlo muy fácilmente, que este indicador, hasta el momento, no hace nada más que mostrar las líneas en las que se encuentran los precios correspondientes a una posición determinada. Además, este indicador no puede detectar que la posición ya no existe. Para que llegue realmente a notar esa condición, es necesario que tú, como operador, cambies el marco temporal del gráfico.

Así, el indicador percibirá que una posición fue cerrada. Al notar esto, abandonará el gráfico y, de esta manera, será eliminado de forma natural, sin ninguna intervención adicional. En el próximo artículo, mejoraremos aún más este indicador, añadiéndole algunas funcionalidades más.

ArchivoDescripción
Experts\Expert Advisor.mq5
Demuestra la interacción entre Chart Trade y el Asesor Experto (es necesario el Mouse Study para la interacción)
Indicators\Chart Trade.mq5Crea la ventana para configurar la orden a ser enviada (es necesario el 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 el 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 la indicación de órdenes de mercado, permitiendo interactuar con ellas y controlarlas
Indicators\Position View.mq5Responsable de la indicación de posiciones de mercado, permitiendo interactuar con ellas y controlarlas
Services\Market Replay.mq5Crea y mantiene el servicio de reproducció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/13167

Archivos adjuntos |
Anexo.zip (779.24 KB)
Del básico al intermedio: FileSave y FileLoad Del básico al intermedio: FileSave y FileLoad
En este artículo se explicarán y explorarán algunas formas de trabajar con las funciones de la biblioteca FileSave y FileLoad. Aunque mucha gente las considera poco prometedoras, debido a algunas limitaciones o dificultades que generan en ciertos escenarios, entender correctamente cómo funcionan estas dos funciones puede ahorrarte mucho trabajo en determinados momentos. Además, son una excelente forma de trabajar con archivos de log.
Operando con el Calendario Económico MQL5 (Parte 8): Optimización del backtesting basado en noticias mediante el filtrado inteligente de eventos y el registro selectivo Operando con el Calendario Económico MQL5 (Parte 8): Optimización del backtesting basado en noticias mediante el filtrado inteligente de eventos y el registro selectivo
En este artículo, optimizamos nuestro calendario económico mediante un filtrado inteligente de eventos y un registro selectivo, con el fin de lograr un backtesting más rápido y claro, tanto en modo en vivo como en modo sin conexión. Optimizamos el procesamiento de eventos y centramos los registros en los eventos críticos relacionados con las operaciones y los paneles de control, lo que mejora la visualización de las estrategias. Estas mejoras permiten probar y perfeccionar sin problemas las estrategias de negociación basadas en noticias.
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.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 22): Panel de correlación Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 22): Panel de correlación
Esta herramienta es un panel de correlación que calcula y muestra coeficientes de correlación en tiempo real entre múltiples pares de divisas. Al visualizar cómo se mueven los pares de divisas en relación unos con otros, se añade un contexto valioso al análisis de la acción del precio y se ayuda a anticipar la dinámica entre mercados. Sigue leyendo para descubrir sus características y aplicaciones.