Simulación de mercado: Position View (VIII)
Introducción
Hola a todos, sean bienvenidos a otro artículo de la serie sobre cómo construir un sistema de repetición / simulador.
En el artículo anterior, Simulación de mercado: Position View (VII), vimos cómo podíamos implementar el indicador de posición para poder cerrar una posición abierta directamente desde el gráfico, interactuando con un objeto disponible en el gráfico. Una vez concluido y funcionando el primer mecanismo, comenzamos a hacer algunas modificaciones para que también fuera posible eliminar las líneas de take profit y stop loss de una posición abierta. Sin embargo, como los cambios necesarios requerían una explicación adecuada, en ese mismo artículo solo mostré los cambios que debían realizarse en el Asesor Experto y aún era necesario mostrar los cambios que debían realizarse en el Indicador de posición.
Los cambios en el código en sí son relativamente pequeños. Al observar al público que ha demostrado interés en este sistema de repetición / simulación, noté que muchos, si no la gran mayoría, no tienen tanta experiencia en programación, menos aún en el tipo de programación que estoy presentando y utilizando. Así, a mi juicio, resulta necesario dar una explicación un poco más detallada, para que todos puedan entender y, si lo desean, modificar los códigos para satisfacer una necesidad particular. Todo el código incluido aquí está orientado única y exclusivamente a fines didácticos. Así que procura estudiar tanto el código como la explicación que daré, porque ahora vamos a trabajar en serio.
Modificando el Indicador de posición
Como pretendo mostrar un poco más en este artículo, veamos de una vez qué quedó pendiente en el artículo anterior. De esta manera, pasaremos directamente a la clase C_IndicatorPosition, cuyo nuevo código se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_ElementsTrade.mqh" 05. //+------------------------------------------------------------------+ 06. class C_IndicatorPosition 07. { 08. private : 09. struct st00 10. { 11. ulong ticket; 12. color corPrice, corTake, corStop; 13. string szShortName; 14. }m_Infos; 15. C_ElementsTrade *Open, 16. *Stop, 17. *Take; 18. //+------------------------------------------------------------------+ 19. public : 20. //+------------------------------------------------------------------+ 21. C_IndicatorPosition(color corPrice, color corTake, color corStop) 22. { 23. ZeroMemory(m_Infos); 24. m_Infos.corPrice = corPrice; 25. m_Infos.corTake = corTake; 26. m_Infos.corStop = corStop; 27. Open = Take = Stop = NULL; 28. } 29. //+------------------------------------------------------------------+ 30. ~C_IndicatorPosition() 31. { 32. delete Open; 33. delete Take; 34. delete Stop; 35. } 36. //+------------------------------------------------------------------+ 37. bool CheckCatch(ulong ticket) 38. { 39. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 40. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 41. if (ObjectFind(0, m_Infos.szShortName) >= 0) 42. { 43. m_Infos.ticket = 0; 44. return false; 45. } 46. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 47. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 48. 49. return true; 50. } 51. //+------------------------------------------------------------------+ 52. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 53. { 54. double value; 55. 56. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 57. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 58. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 59. switch (id) 60. { 61. case CHARTEVENT_CUSTOM + evUpdate_Position: 62. if (lparam != m_Infos.ticket) return; 63. if (!PositionSelectByTicket(m_Infos.ticket)) 64. { 65. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 66. return; 67. }; 68. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, m_Infos.corPrice, ePriorityNull, "Position opening price."); 69. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, m_Infos.corTake, ePriorityOrders, "Take Profit point."); 70. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, m_Infos.corStop, (EnumPriority)(ePriorityOrders + 1), "Stop Loss point."); 71. (*Open).UpdatePrice(PositionGetDouble(POSITION_PRICE_OPEN)); 72. if ((value = PositionGetDouble(POSITION_TP)) > 0) (*Take).UpdatePrice(value); else 73. { 74. delete Take; 75. Take = NULL; 76. } 77. if ((value = PositionGetDouble(POSITION_SL)) > 0) (*Stop).UpdatePrice(value); else 78. { 79. delete Stop; 80. Stop = NULL; 81. } 82. break; 83. } 84. ChartRedraw(); 85. } 86. //+------------------------------------------------------------------+ 87. }; 88. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Bien, seguramente no entiendes absolutamente nada de lo que ocurrió aquí, porque el código cambió de forma bastante abrupta de la noche a la mañana. Este cambio fue intencional y oportuno. Esto se debe a que necesitamos comenzar a separar algunas partes del código para que, cuando vayamos a desarrollar la parte sobre órdenes pendientes que se verá en el futuro, no necesitemos crear mucho código ni tener un código tan denso que dificulte la eliminación de partes que podrían reutilizarse. Entonces, mi querido lector, no te asustes ni te preocupes a medida que aparezcan los cambios. Estos ocurrirán a medida que sea necesario separar partes que puedan reutilizarse en el futuro.
Muy bien, al observar el código anterior, notas de inmediato, en la línea cuatro, que incluimos un nuevo archivo de encabezado. Lo veremos hacia el final de este mismo artículo. Por ahora, no te preocupes por él. Vamos a concentrarnos en este archivo. Así, en la línea 15, declaramos tres punteros a la clase C_ElementsTrade. Estos punteros tienen como objetivo permitirnos acceder de forma controlada al código de la clase
C_ElementsTrade. Presta atención a lo siguiente: estoy usando tres punteros porque, en la clase C_ElementsTrade, crearemos todo lo necesario para que el indicador de posición se muestre correctamente. Como varios componentes serán comunes a las indicaciones que se mostrarán, como, por ejemplo, el botón de cerrar, la línea que indica el punto del precio, entre otros elementos que añadiremos después, para simplificar, podemos reunir todo en una única clase. Y, cuando referenciemos la clase que crea tales elementos gráficos, podemos cambiar fácilmente de un tipo de estructura a otro. O, mejor dicho, podemos reutilizar todos los elementos de la indicación de una línea de stop loss en la indicación de take profit, siempre que, por supuesto, creemos alguna diferenciación aquí en el código, de modo que el operador sepa fácilmente qué representa cada indicador. No necesitaremos codificar una clase solo para indicar el stop loss y otra solo para el take profit. No sé si mi explicación quedó clara. Pero, al ver más detalles sobre el código, tú, mi querido lector y entusiasta, entenderás lo que ocurre.
Pues bien, normalmente, el compilador inicializa las variables con un valor adecuado. Pero, como queremos estar seguros de esto, la inicializamos en el constructor. Esto ocurre en la línea 27, donde indicamos que los punteros se inicializarán con un valor NULL. Presta mucha atención a este detalle, porque es importante que lo sepas. También conviene destacar que, en el destructor, liberamos la memoria asociada a los punteros, devolviendo así la memoria al sistema. Esto se hace entre las líneas 32 y 34. Pero, en este punto, tú, mi querido entusiasta, puedes preguntarte: ¿por qué usar un comando delete si todavía no hemos asignado memoria mediante el comando new? Esto no tiene sentido. En efecto, no tiene sentido a primera vista. Pero no debes descuidar las variables que son punteros. Es muy común que, durante la ejecución de programas, en algún momento se genere un error. Esto ocurre porque el programa ejecutó alguna operación que implica punteros. Y, como nuestros punteros estarán declarados en la clase C_IndicatorPosition, cuando se cierre la clase, debemos garantizar que la memoria se libere correctamente.
Bien. Esta fue la explicación preliminar. Ahora veamos dónde hubo más cambios en el código original. Así que vayamos directamente a la línea 52, donde tenemos la implementación del procedimiento DispatchMessage. En este punto, si no prestas atención, te perderás, ya que aquí ocurre la magia. Observa que el resto del código permaneció prácticamente igual que antes. En este procedimiento de la línea 52, tenemos varias cosas que pueden generarte confusión, mi querido amigo lector. Lo primero que puede resultar muy confuso es el código que aparece entre las líneas 56 y 58. ¿Por qué hago estas comprobaciones antes de llamar al procedimiento DispatchMessage de cada uno de los elementos que añadiremos al gráfico? El motivo es muy simple. En el primer momento en que llamamos a este procedimiento de la línea 52, NO TENEMOS NINGÚN ELEMENTO EN EL GRÁFICO. Entonces, si no comprobamos si los punteros apuntan realmente a algo válido en la memoria, correremos el riesgo de ejecutar 'basura'. Pero, como MQL5 parece tener algún mecanismo de seguridad, al intentar ejecutar 'basura', el código fallaría, lo que provocaría la salida abrupta del indicador del gráfico. Bien, entendido. ¿Tendríamos que hacer esta comprobación en todo momento? Sí. Hay que hacerla cada vez que se ejecute este procedimiento de la línea 52. El motivo lo entenderás en breve. Pasemos al siguiente punto, donde comenzamos a manejar el evento que este procedimiento espera. El manejo de ese evento comienza en la línea 61. Ahora presta mucha, muchísima atención, porque lo primero que hacemos es comprobar, en la línea 62, si el valor del parámetro lparam es el mismo que el ticket observado por el indicador. Si los valores son diferentes, interrumpimos el procesamiento en este punto. Si son iguales, verificamos, en la línea 63, si la posición existe en el servidor. Si ya no existe, en la línea 65, eliminamos el indicador de posición del gráfico y, en la línea 66, terminamos el manejo del mensaje. Si estas primeras comprobaciones se completan correctamente, en la línea 68, comprobaremos si el puntero de los elementos del precio de apertura ya se creó. Esta comprobación solo será verdadera en la primera llamada que ejecutemos. En todas las demás, será falsa. Como esta es la primera llamada, usaremos el operador new para llamar al constructor. Es importante que entiendas esto. El motivo es que puedes indicarle al constructor que cree una estructura determinada para los elementos. Así, usando la misma clase, podrás crear un indicador para el take profit, otro para el stop loss y otro para el precio de apertura. Esto es precisamente lo que hacemos entre las líneas 68 y 70. Observa que, en cualquier caso, inicializaremos los punteros, salvo los que ya se hayan inicializado. Podemos mejorar aún más este sistema, para evitar llamadas innecesarias. Pero veremos esto más adelante. Primero, es necesario que tú, mi querido lector y entusiasta, entiendas cómo funciona todo.
Una vez inicializados los tres punteros en las líneas 68 y 70, en la línea 71 le indicamos a C_ElementsTrade, referenciada por el puntero OPEN, que actualice el precio. Tal vez pienses o imagines que esto es una tontería y que podríamos hacerlo directamente en el constructor. De hecho, podríamos. Claro, siempre que estuviéramos seguros de que la cuenta es de tipo HEDGING. Porque, si usamos una cuenta NETTING, el precio puede ser diferente, debido a que el servidor nos informa un valor que corresponde al promedio de la posición. Por eso no paso el valor directamente al constructor de la clase C_ElementsTrade. En el futuro, lo haremos de otra manera. No te desesperes, mi querido lector. Vamos paso a paso. Entiende primero este código más simple para, en el futuro, entender uno más elaborado.
Muy bien. Ahora viene la parte realmente interesante de este procedimiento DispatchMessage. Lo que explicaré aquí también sirve para lo que se hará entre las líneas 77 y 81. No es complicado. Solo presta atención y entenderás fácilmente lo que ocurre. Primero, obtenemos el valor que necesitamos y lo asignamos a la variable value. Después, comprobamos si el valor es mayor que cero. Si lo es, significa que existe una línea de nivel límite, ya sea de take profit o stop loss. En cualquier caso, si el valor existe, pasaremos ese valor al objeto adecuado de C_ElementsTrade. Es decir, haremos algo parecido a lo que se hizo en la línea 71. Ahora bien, hay un detalle diferente de lo que se hizo en el caso de la actualización del precio de apertura. Si el valor comprobado es cero, esto indicará que no tenemos el límite esperado. Así, los elementos que representan el precio deberán eliminarse del gráfico. Tal vez esperabas ver que esto se hiciera aquí, en la clase C_IndicatorPosition. Sin embargo, al revisar el código, no parece encontrarse el punto donde los objetos se eliminan del gráfico. Pero sí, está aquí. Es precisamente en la línea 74 donde hacemos esto, ya que llamamos al destructor adecuado. Como la ejecución del comando delete no pone el puntero en NULL, sino que solo libera, o desasigna, la memoria que se usaba, necesitamos indicar explícitamente al puntero que ahora es un puntero nulo. Esto se hace en la línea 75. Presta atención a esto. La dirección a la que apunta el puntero puede contener basura. Como el código espera comprobar si el valor del puntero es nulo o no, necesitamos asignarlo explícitamente como nulo. No porque siempre sea necesario hacerlo, sino porque el código no se cerrará y, en las comprobaciones de las líneas 56 a 58, el puntero podría apuntar a basura. Esto haría fallar el código en cuanto eliminaras del servidor el precio límite correspondiente, ya sea el valor de stop loss o take profit.
Muy bien, hasta este momento solo me centré en explicar la parte relativa al código de la clase C_IndicatorPosition. La explicación no estará completa hasta que veamos la clase C_ElementsTrade. Esta es precisamente la clase que hace todo el trabajo por nosotros. Para separar adecuadamente los temas, vamos a ver esto en un nuevo tema.
Inicio de la clase C_ElementsTrade
Bien, hasta ahora, todo lo visto hasta ahora puede parecer complicado y confuso para muchos. Si este fue tu caso, mi querido lector, no te desanimes. Vuelve algunos artículos atrás y comienza a estudiar todo con un poco más de calma, porque intento mostrarlo todo de la forma más sencilla y didáctica posible. Sé que mucho de lo que estoy mostrando parece extremadamente complicado y difícil de entender e imaginar. Esto ocurre porque no esperabas, o no imaginabas, que esto que estoy haciendo fuera realmente posible, ya que prácticamente nadie había mostrado antes algo así.
En cualquier caso, continuemos. Ahora vamos a empezar a ver un nuevo código, con el que trabajaremos bastante. A partir de ahora, no verás con tanta frecuencia modificaciones en los códigos vistos anteriormente. A partir de ahora, nuestro foco estará en este nivel. Bien. La nueva clase que crearemos puede verse a continuación, con su código completo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 05. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 06. //+------------------------------------------------------------------+ 07. #define def_PathBtns "Images\\Market Replay\\Orders\\" 08. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 09. #resource "\\" + def_Btn_Close; 10. //+------------------------------------------------------------------+ 11. #include "..\Auxiliar\C_Terminal.mqh" 12. //+------------------------------------------------------------------+ 13. class C_ElementsTrade : private C_Terminal 14. { 15. private : 16. //+------------------------------------------------------------------+ 17. struct st00 18. { 19. ulong ticket; 20. string szPrefixName; 21. EnumEvents ev; 22. double price; 23. }m_Info; 24. //+------------------------------------------------------------------+ 25. void UpdateViewPort(void) 26. { 27. int x, y; 28. 29. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 30. ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price); 31. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, 130); 32. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 33. } 34. //+------------------------------------------------------------------+ 35. public : 36. //+------------------------------------------------------------------+ 37. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 38. :C_Terminal() 39. { 40. string szObj; 41. 42. ZeroMemory(m_Info); 43. m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 44. CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio); 45. ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr); 46. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 47. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 48. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityOrders + 1)); 49. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 50. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 51. } 52. //+------------------------------------------------------------------+ 53. ~C_ElementsTrade() 54. { 55. if (m_Info.szPrefixName != "") 56. ObjectsDeleteAll(0, m_Info.szPrefixName); 57. } 58. //+------------------------------------------------------------------+ 59. inline void UpdatePrice(const double price) 60. { 61. m_Info.price = price; 62. UpdateViewPort(); 63. } 64. //+------------------------------------------------------------------+ 65. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 66. { 67. switch (id) 68. { 69. case CHARTEVENT_OBJECT_CLICK: 70. if (sparam == def_NameBtnClose) 71. { 72. if (PositionSelectByTicket(m_Info.ticket)) switch (m_Info.ev) 73. { 74. case evMsgClosePositionEA: 75. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 76. break; 77. case evMsgCloseTakeProfit: 78. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 79. break; 80. case evMsgCloseStopLoss: 81. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 82. break; 83. }; 84. } 85. break; 86. case CHARTEVENT_CHART_CHANGE: 87. UpdateViewPort(); 88. break; 89. } 90. } 91. //+------------------------------------------------------------------+ 92. }; 93. //+------------------------------------------------------------------+ 94. #undef def_Btn_Close 95. #undef def_PathBtns 96. //+------------------------------------------------------------------+ 97. #undef def_NameBtnClose 98. #undef def_NameHLine 99. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Ahora sí, el tema se complicó de verdad, ya que muchos podrían esperar un código mucho más grande, o con mucha más información o con más elementos en funcionamiento. Al observar el código anterior, queda claro que el trabajo se realiza de una manera completamente diferente de la que muchos esperaban. Veamos qué ocurre aquí. Y, sobre todo: veamos por qué este código puede hacer lo que promete, que es permitir al operador usar un elemento gráfico, que sería un botón presente en la línea de precio, para cerrar o eliminar algo. En este caso, se trataría de los puntos de take profit y stop loss. Al mismo tiempo, nos permite cerrar una posición abierta. Todo esto de una forma bastante simple y elegante.
Muy bien, si llegaste hasta aquí, significa que realmente tienes curiosidad por entender cómo funciona este mecanismo. Entonces, vamos a empezar por lo siguiente: olvida toda aquella maraña de asignar nombres y más nombres a los objetos del gráfico. Eso solo complica innecesariamente nuestro trabajo. Procura usar siempre una nomenclatura simple y eficaz. Segundo: no intentes controlar ni administrar lo que está en el gráfico. Deja que MetaTrader 5 se encargue de eso por ti. No usaremos listas de objetos ni estructuras de ese tipo en esta implementación. Solo crearemos los objetos de forma adecuada y dejaremos que MetaTrader 5 los gestione de la mejor forma posible, manteniendo la lista de objetos presentes en el gráfico bajo el control de MetaTrader 5.
Un detalle respecto de lo que acabo de decir: MetaTrader 5 no es una herramienta mágica. No funcionará bien si lo usas de forma incorrecta. Es necesario que entiendas MetaTrader 5 para que realmente puedas aprovechar lo que ofrece. Por eso, lo primero que encontramos en el código anterior son justamente algunas definiciones. Observa, en las líneas cuatro y cinco, la presencia de dos definiciones que se usarán solo en este archivo de encabezado. Digo solo porque, en las líneas 97 y 98, eliminamos estas mismas definiciones. Si intentas usarlas fuera de este archivo, el compilador generará errores. El código no se compilará. Muy bien, observa lo que tenemos en estas dos definiciones. Tenemos una variable que veremos después y una string. Solo esto, nada más. Esto es más que suficiente para que MetaTrader 5 pueda gestionar esos datos por nosotros, manteniendo en una lista interna los objetos que crearemos en este código. No necesitamos una lista extra solo para esto. Deja que MetaTrader 5 se encargue de la lista de objetos.
Las líneas 7 a 9 ya fueron comentadas anteriormente y no merecen una mención especial. Entonces, podemos pasar al código de la clase, y observa que aquí hay pocos procedimientos y funciones. Apenas estamos empezando a crear este código. Vamos con calma. Lo primero que debes hacer al revisar una clase es buscar sus procedimientos de creación y destrucción. Estos son el constructor y el destructor. Pues bien, en la línea 37 tenemos la declaración del constructor de la clase. Ahora quiero que vuelvas al tema anterior y veas qué representa cada uno de estos parámetros en relación con lo explicado anteriormente. Observa que el código de este constructor es bastante simple. Lo único que hacemos es crear los objetos que se mostrarán en el gráfico. Básicamente, tomo el código que existía antes en la clase C_IndicatorPosition y lo replico aquí. Así de simple. A diferencia de lo que ocurría antes, cuando solo se creaba una línea horizontal, en este caso tenemos dos objetos creados. Uno es la línea, que se crea en la línea 44 del código. Y el otro es el botón de interacción. Este se crea en la línea 48 del código. Ahora viene la parte en la que aquellas definiciones de las líneas cuatro y cinco empezarán a tener sentido. Observa que, en la línea 43, defino el valor de la misma variable usada en las definiciones. Es decir, cuando usamos las definiciones para decirle a MetaTrader 5: quiero cambiar algo en un objeto determinado, o quiero que me digas si ese objeto recibió un evento. MetaTrader 5 entenderá y responderá de manera adecuada a nuestra petición, ejecutando exactamente la acción solicitada. Por eso, no necesitamos una lista extra de objetos. MetaTrader 5 gestionará esa organización por nosotros. Y esto es excelente, porque nos ahorra bastante gestión manual. Pero, antes de pasar al destructor, quiero llamar tu atención sobre un detalle, mi querido lector. ¿Recuerdas que, antes, cuando íbamos a posicionar el botón de interacción, necesitábamos ajustar su posición restando un valor? Pues bien, como dije en aquella ocasión, tenemos una forma mejor de hacerlo. La forma es usar la línea 50. Pero hay un detalle: con esa línea, el botón se posicionará en el punto que indiquemos. Veremos esto con más detalle en el futuro. Por ahora, observa que ya no necesitamos hacer aquel ajuste.
Bien. Así como el constructor refleja lo que ya existía antes, lo mismo se aplica al destructor de la línea 53. No voy a destacarlo. Solo lo menciono.
Observa que, además de lo visto, tenemos otros tres procedimientos. Aunque podrían ser solo dos. Por cuestiones prácticas, decidí dividir la implementación en tres procedimientos. El procedimiento de la línea 59 es básico, solo delega la ejecución en el procedimiento de la línea 25. Este procedimiento también es bastante básico. Tiene como única tarea mostrar los objetos en el gráfico en los lugares adecuados. Esto no merece una mención especial, ya que llevamos bastante tiempo hablando de estos objetos. Sin embargo, el procedimiento de la línea 65 sí merece una mención especial, ya que, sin él, nada de lo que ves en el gráfico sucederá realmente. Veamos ahora este procedimiento con calma.
En este procedimiento DispatchMesssage, que es el núcleo de esta clase, al comienzo manejamos dos eventos. Uno de clic en los objetos y otro de actualización del gráfico. Bien, el evento de actualización, que aparece en la línea 86, es simple y directo. Solo posiciona los elementos u objetos en el gráfico, algo que ya hacíamos antes. No obstante, el evento de clic es un poco más complicado que en la versión anterior. No te preocupes. Es solo un poco más complicado. No es nada del otro mundo.
Observa que, cuando se ejecuta la línea 69, lo primero que hacemos es verificar si el objeto informado por MetaTrader 5 es el mismo que esperamos manejar. Es decir, un objeto tipo CLOSE. Una vez más, no intentes pensar de manera convencional. Cada instancia de C_ElementsTrade pertenecerá a uno y solo uno de los datos que necesitaremos mostrar. Es decir, no necesitamos saber si el objeto CLOSE corresponde al take profit, al stop loss o al precio de apertura. Eso tiene poca importancia. Lo que realmente importa es que el objeto es tipo CLOSE. Solo eso; determinar a qué pertenece se resolverá después.
Así, si la comprobación del objeto es verdadera, haremos una nueva comprobación. En la línea 72. En esta comprobación verificamos si el ticket almacenado en la clase corresponde a una posición válida. Esta comprobación es importante para que podamos distinguir órdenes de posiciones. Aunque todavía no hemos hablado sobre las órdenes, ya estamos preparando el terreno para que, cuando se implementen, no tengamos que cambiar demasiado el código. Si esta nueva comprobación también se supera, entraremos en un pequeño mecanismo. Este ejecutará lo que muchos esperarían hacer al inicio del código.
Aquí vamos a comprobar qué elemento recibió el evento de clic. ¿Cómo puede ser? ¿MetaTrader 5 no nos informará esto? En cierta forma, MetaTrader 5 nos lo indica correctamente. Como quiero explicar con calma cómo ocurre esto, usamos otro enfoque. Así, querido lector y entusiasta, más adelante podrás entender cómo trabajar de una forma mucho más elaborada. En cualquier caso, la idea es la misma. En la clase actual existe un valor que se define durante la fase de creación del objeto. Es decir, vuelve al código del constructor y observa que, en la línea 43, almacenamos el mismo valor que se comprobará aquí, entre las líneas 73 y 83.
Observa ahora la magia. Cuando, en la clase C_IndicatorPosition, dices que la clase C_ElementsTrade deberá crear los elementos u objetos gráficos para representar un take profit, en realidad estarás estableciendo una separación entre los conjuntos que se van a crear. Esta separación se establece en la línea 43. Sin embargo, solo se usa entre las líneas 73 y 83.
Observa lo siguiente: si el conjunto creado nuevamente en la clase C_IndicadorPosition corresponde a un stop loss, cuando hagas clic en el botón de interacción para el Stop Loss, el código que realmente se ejecutará será el de la línea 81, enviando un mensaje al Asesor Experto para decirle: mira, Asesor Experto, quiero que se elimine el valor de stop loss. El Asesor Experto solicitará al servidor que esto ocurra. Cuando el servidor responda, indicando si la solicitud fue aceptada o no, el Asesor Experto enviará un mensaje al gráfico, informando a todos de que ocurrió un evento. El Indicador de posición espera este evento. Y, en la clase C_IndicatorPosition, eliminará de la memoria la instancia de C_ElementsTrade vinculada al stop loss. Así, todos los objetos asociados a esa instancia serán eliminados. El destructor de la clase indicará a MetaTrader 5 que elimine del gráfico los objetos con un nombre determinado.
Por eso, dije al comienzo del tema que no necesitas preocuparte por ciertos detalles de gestión. Si procedes de manera adecuada, tendrás la ayuda de MetaTrader 5 para realizar las tareas que deseas.
Consideraciones finales
En este artículo, comenzamos a explorar más profundamente ciertos detalles. Como quiero explicar y hacer que las cosas sean lo más didácticas posible, a muchos les puede parecer que avanzamos despacio. Pero, si realmente diera el ritmo que considero adecuado, al final, pocos podrían entender lo que realmente ocurre. Entre esos pocos, aún menos lograrían modificar el código para adaptarlo a sus propósitos particulares. Así que, si ya tienes buenos conocimientos de programación, ten paciencia con quienes están empezando.
Para quienes deseen comprobar el funcionamiento del sistema, en el anexo dejo los archivos necesarios ya compilados. Para que todo funcione de forma adecuada, no deberás cambiar la estructura de directorios usada en el anexo. Solo extrae y prueba el sistema. Para ello, usa el indicador de mouse, el indicador Chart Trade y el Asesor Experto. Deberás colocar estas tres aplicaciones en el gráfico. El indicador de posición se cargará automáticamente cuando sea necesario.
Un último detalle. Aunque desees usar solo el Asesor Experto y el sistema de órdenes de un clic de MetaTrader 5, tendrás el mismo tipo de resultado que se obtendría con todas las aplicaciones en el gráfico. Aun así, sugiero que empieces a acostumbrarte a usar el conjunto de las tres aplicaciones. Todavía haremos mejoras en el sistema. Algunas cosas no funcionarán si usas el sistema de forma diferente.
| Archivo | Descripció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.mq5 | Crea la ventana para configurar la orden que se va a enviar (Es necesario usar Mouse Study para la interacción) |
| Indicators\Market Replay.mq5 | Crea los controles para interactuar con el servicio de repetición / simulador (Es necesario usar Mouse Study para la interacción) |
| Indicators\Mouse Study.mq5 | Permite 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.mq5 | Responsable de indicar órdenes de mercado, permitiendo interactuar con ellas y controlarlas |
| Indicators\Position View.mq5 | Responsable de indicar posiciones de mercado, permitiendo interactuar con ellas y controlarlas |
| Services\Market Replay.mq5 | Crea 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/13257
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Introducción a MQL5 (Parte 19): Automatización de la detección de las ondas de Wolfe
Optimización Extrema — Extremal Optimization (EO)
De novato a experto: Noticias animadas utilizando MQL5 (VII) Estrategia para el trading de noticias tras el impacto
Del básico al intermedio: Como burbujas de jabón
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso