English Русский Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición (Parte 72): Una comunicación inesperada (I)

Desarrollo de un sistema de repetición (Parte 72): Una comunicación inesperada (I)

MetaTrader 5Ejemplos |
164 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 71): Ajuste del tiempo (IV)", expliqué y mostré cómo tú, estimado lector, deberías proceder para añadir al sistema de repetición/simulador lo que se presentó en otro artículo.

En este caso, el artículo "Desarrollo de un sistema de repetición (Parte 70): Ajuste del tiempo (III)", donde utilizamos un servicio de prueba para comprender más claramente cómo trabajar con eventos de libro de órdenes. Esto ocurre cuando el enfoque está en un símbolo personalizado.

En estos dos últimos artículos, todo resultó muy interesante. Esto se debe a la forma en que necesitamos trabajar para obtener los resultados deseados. Creo que muchos aprendieron y entendieron cómo utilizar correctamente el libro de órdenes en MetaTrader 5. Una vez más, el enfoque está completamente puesto en un símbolo personalizado. No olvides esto en ningún momento.

Fue bastante curioso ver que podíamos permitir que el indicador de mouse utilizara la función OnCalculate de MetaTrader 5 simplemente al agregar el libro de órdenes, ya que este coloca los datos en matrices. Esto agiliza mucho las cosas, ya que no necesitamos utilizar la función iSpread para obtener el spread de la barra.

Sin el conocimiento mostrado en los dos artículos, no es posible obtener los datos entre los argumentos de la función OnCalculate cuando el timeframe es mayor a un minuto. Exceptuando este hecho, no tenemos problemas para obtener los datos a través de los argumentos de OnCalculate. Sin embargo, a partir del momento en que se empiezan a usar eventos personalizados del libro de órdenes, es posible leer el spread directamente entre los argumentos de OnCalculate.

Sin embargo, al final del último artículo expliqué el problema que debemos resolver. Si no se resuelve, tú o el usuario de la aplicación no podréis utilizarla correctamente. Así que, para quienes están llegando ahora a este artículo, recordemos rápidamente de qué se trata.


Recordamos el problema

El problema que debemos corregir es una falla que se produce siempre que modificamos el timeframe. Aunque no se trate de un fallo catastrófico, resulta bastante molesto. Ocurre de la siguiente manera: cuando iniciamos el sistema de repetición/simulador, podemos seleccionar el timeframe. Una vez que se ha abierto el gráfico, este permanecerá en el marco temporal definido.

Si tú o el usuario modifican el timeframe usando MetaTrader 5, la información proporcionada por el indicador de mouse cambiará de «subasta» a «mercado cerrado». Además, el indicador de control desaparecerá del gráfico, por lo que no se podrá interactuar con él.

A pesar de la incomodidad que esto nos provoca, el hecho es que esta falla se produce porque MetaTrader 5 vuelve a colocar los indicadores en el gráfico y llama de inmediato a la función OnInit. En cualquier caso, MetaTrader 5 está haciendo lo que fue diseñado para hacer. Somos nosotros quienes estamos creando formas de utilizar el programa de manera completamente diferente. Esto se debe a que necesitamos que funcione de un modo distinto.

Entonces, ¿qué sucede en estas situaciones? Cuando se produce una falla, debemos esperar a que el servicio desencadene un evento personalizado para el indicador de control para que se inicialice con nuevos valores. El problema es que esto puede llevar bastante tiempo. Además, para que el servicio realmente dispare un evento personalizado que permita la inicialización del indicador de control con otros valores, es necesario que el servicio esté en modo reproducción.

Si el servicio no está en modo reproducción, el indicador de control nunca recibirá el evento personalizado necesario para su inicialización, por lo que quedará totalmente inaccesible para el usuario. En este caso, sería necesario cerrar el servicio y luego volver a abrirlo. Es decir, un contratiempo. Ahora, si el servicio está en modo reproducción, tendremos que esperar a que se dispare el mencionado evento personalizado. Cuando esto ocurra, los controles del indicador de control volverán a estar accesibles. En ese momento, el usuario deberá pausar el servicio de repetición/simulación y luego volver a ponerlo en modo reproducción. Esto hará que la información presentada por el indicador de mouse, que es el tiempo restante en la barra, vuelva a mostrarse correctamente. Sin embargo, si el usuario vuelve a modificar el timeframe, todo el problema se repetirá.

En el artículo anterior expliqué este problema con más detalle. Aquí simplemente lo recuerdo y, al mismo tiempo, presento el problema a quienes están empezando a seguir esta secuencia en este artículo. De todos modos, dejé en el artículo anterior un pequeño desafío para mis estimados lectores. El desafío consistía en imaginar una forma de corregir esta falla. Aunque muchos puedan pensar que la programación es el arte de escribir código que puede ser ejecutado por un ordenador, en realidad es mucho más que eso. Escribir código es la parte fácil. Lo difícil es pensar en una manera de resolverlos. Por lo tanto, la programación es, en realidad, el arte de resolver problemas mediante programas.

Sinceramente, no me importa si tú, estimado lector, lograste resolver el problema o no. Solo me gustaría que, si no lo has conseguido, al menos hayas intentado hacerlo. Resolver problemas y pensar en soluciones es, sin duda, un arte. Escribir código es solo una formalidad. Pero, veamos cómo se resolverá este problema.


Primera alternativa de solución

Si realmente has entendido el problema, ya pensarás en la siguiente solución: implementar una forma de verificar cuándo se ha modificado el timeframe. Tan pronto como esto ocurra, haremos que los indicadores puedan restaurar su estado antes de haber sido eliminados del gráfico. De esta manera, cuando MetaTrader 5 vuelva a colocarlos en el gráfico, los indicadores, tanto el de control como el de mouse, conocerán su último estado y podrán reanudar desde ese punto. Excelente idea. Sin embargo, en este caso tenemos un inconveniente. Cualquier forma que utilices para registrar el último estado en el que se encontraba el indicador no podrá, bajo ninguna circunstancia, estar vinculada directamente al mismo. Esto implica que dicha información debe estar disponible para el indicador tan pronto como se inicie el código OnInit. De lo contrario, surgirán problemas.

Existen varias formas de lograrlo, pero todas implican una implementación y pruebas adicionales. Estas se llevarán a cabo con el fin de garantizar que los datos del último estado puedan o no utilizarse. Personalmente, considero que esta solución es bastante laboriosa, ya que serían necesarias pruebas adicionales solo para verificar si la información puede utilizarse.

Ahora bien, supongamos que realmente deseas hacerlo de esta manera. ¿Cómo se implementaría esto en la práctica? Primero, pensemos en qué deberíamos almacenar. En el caso del indicador de control, sería necesario registrar si estaba en modo pausa o en modo reproducción. Perfecto. Esto parece ser suficiente. Por otro lado, en el caso del indicador de mouse, bastaría con implementar una forma de que este volviera a indicar que el símbolo estaba en subasta. Muy bien. Almacenar esta información es bastante simple. Podrías guardarla en un archivo o en una variable global del terminal. En cualquier caso, esta parte sería relativamente fácil de realizar. Ahora pasemos a los problemas que esto podría generar.

En el caso del indicador de control, no habría mucho que complicarse, ya que este solo se colocaría en el gráfico cuando el servicio de repetición/simulador estuviera en ejecución. Por lo tanto, cualquiera de las dos soluciones —usar un archivo o variables globales del terminal— podría resolver el problema. Simplemente habría que verificar si el archivo o la variable existe. Si existe, usarías su valor. Sin embargo, surge un problema: si transcurre algún tiempo, los datos podrían no ser compatibles con el estado actual del servicio. Considera lo siguiente: sería necesario garantizar que el servicio elimine la variable global del terminal o el archivo tan pronto como finalice.

Es decir, más trabajo de programación y más cosas de las que preocuparse. Volvamos ahora al caso del indicador de mouse. Si inicializas este indicador con el valor de símbolo en subasta, podría causar problemas al colocarlo en el gráfico de un símbolo real. La razón es que el mercado podría estar cerrado, pero el indicador de mouse mostraría que el símbolo está en subasta. Además, tengo planes para mejorar aún más el indicador de mouse, por lo que no nos ayuda en absoluto que inicie indicando que el símbolo está en subasta. Al contrario, crea una capa adicional de complicación futura y quiero evitar al máximo la aparición de tales complicaciones.

En resumen, usar una variable global del terminal está completamente descartado. Esto se debe a que crearíamos una doble forma de inicializar los valores de los indicadores. Del mismo modo, usar un archivo generaría el mismo tipo de inconveniente. Por lo tanto, esta solución no es viable. Necesitamos otra solución que, al mismo tiempo esté vinculada al indicador y nos permita analizar lo que está sucediendo en el gráfico.


Segunda alternativa de solución

Es muy probable que alguien sugiriera que el servicio revisara el timeframe actual del símbolo personalizado. Esta sería, de hecho, una solución muy ingeniosa. Así se evitaría el problema de tener que registrar cualquier cosa sobre los indicadores. Recuerda que el servicio de repetición/simulación sabe cuál es el estado del indicador en cualquier momento de la ejecución. Sin embargo, a pesar de que esta solución es bastante ingeniosa, tiene un pequeño problema: no hay una forma de obtener el timeframe directamente desde el servicio.

Pero antes de descartar esta alternativa, pensemos un poco. Si logramos detectar el cambio de timeframe de alguna manera, solo necesitaríamos que el servicio llamara a UpdateIndicatorControl, presente en el archivo de cabecera C_Replay.mqh, y todo estaría resuelto. De hecho, esto es casi cierto. Tendríamos que realizar una pequeña edición en el procedimiento UpdateIndicatorControl, pero es mucho mejor que crear una segunda forma de inicializar los valores del indicador. Entonces, el pequeño problema radica en hacer que el servicio sea capaz de detectar de alguna forma que el timeframe ha sido modificado. Así, podría utilizar los métodos ya implementados para que los indicadores sepan cómo deben inicializarse. Incluso si fuera necesario realizar algún cambio en la implementación del servicio para usar UpdateIndicatorControl, sería mucho menor que modificar todos los indicadores para ajustarlos. Por lo tanto, merece la pena considerar esta alternativa.


Tercera alternativa de solución

Muy bien, ¿qué os parece si intentamos algo un poco más exótico? Vamos a combinar ambos mundos en uno solo, o mejor dicho, las dos alternativas en una sola. Pero hagámoslo sin usar variables globales del terminal ni archivos. En su lugar, utilizaremos el buffer de datos como medio para transferir la información que necesitamos, en este caso, el valor del timeframe. Así, el servicio podrá seguir lo que está sucediendo. Así, cuando se modifique el timeframe, el servicio podrá detectarlo. Cuando esto ocurra, podremos hacer que el servicio indique a los indicadores que necesitan ser actualizados.

Así que nuestra verdadera dificultad estriba en colocar la información del timeframe en el buffer de datos y, luego, leer esta información en el servicio. Cuando el usuario la modifica, toda la aplicación recibe efectivamente un evento personalizado del servicio. Este evento permitirá que los indicadores de mouse reciban el valor correcto para mostrárselo al usuario, al igual que el indicador de control. De este modo, se evitará que este último quede inaccesible al manejar el modo reproducción o pausa.


Iniciamos la prueba de implementación

Contrario a lo que muchos piensan, nosotros, los programadores, probamos las cosas antes de implementarlas. No modificamos directamente un programa o aplicación que ya está muy avanzado. Siempre que necesitamos implementar algo nuevo, primero creamos una versión de prueba. Esta versión es lo más simple posible, ajustamos en ella los elementos necesarios y estudiamos cómo debería implementarse el proceso final. Así nos ahorramos tener que eliminar partes que se añadieron y resultaron inadecuadas para el código final.

Vamos a crear un indicador muy sencillo para verificar los cambios en el timeframe. El código fuente completo de este indicador puede verse 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. int OnInit()
08. {
09.    Print(_Period);
10. 
11.    return
     INIT_SUCCEEDED;
12. }
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.    return rates_total;
17. }
18. //+------------------------------------------------------------------+

Código fuente del indicador de prueba

¿Qué hace este pequeño código? Bueno, todo sucede en la línea nueve. Es decir, imprime en el terminal la información contenida en la variable interna _Period. Se puede observar un ejemplo de ejecución en la siguiente imagen:

Image 01

Resultado de la ejecución

Observa algo curioso aquí. En la columna Source de esta imagen vemos el nombre del indicador, pero lo que realmente nos interesa es lo que hay entre paréntesis y, más concretamente, lo que aparece después de la coma. Fíjate que he modificado el timeframe algunas veces y, en cada una de ellas, hay un valor diferente impreso en la columna de mensajes. Puede parecer obvio, pero olvida por un momento el hecho de que los valores son diferentes. También olvida intentar encontrar algún tipo de lógica. Lo que realmente nos interesa es cuál es el valor máximo que _Period puede contener. Bien. MetaTrader 5 nos permite usar el periodo mensual. En la imagen en cuestión, puedes ver este valor en la penúltima línea; es decir, el mayor valor es 49153. ¿Por qué es importante conocer este valor? Porque necesitamos una forma de hacer que el servicio detecte que el valor ha cambiado. Pero no podemos permitirnos usar cualquier longitud de bits. Necesitamos que la cantidad de bits utilizada sea la menor posible. La razón la comprenderás en breve.

Haremos una pequeña modificación en el código anterior. El resultado erá el 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. int OnInit()
08. {
09.    Print(_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))));
10. 
11.    return INIT_SUCCEEDED;
12. }
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.    return rates_total;
17. }
18. //+------------------------------------------------------------------+

Código fuente del indicador de prueba

Muy bien, antes de analizar lo que hace esta nueva línea, observa en la siguiente imagen el resultado de su ejecución.

Image 02

Resultado de la ejecución

Ahora presta atención al siguiente hecho. Antes se necesitaban 16 bits para registrar el valor necesario y detectar que el timeframe había cambiado. Ahora observa el valor máximo posible: 96. ¡Increíble! ¿Podemos hacer esto? Sí, podemos y lo haremos. Esto se debe a que, aparentemente, MetaTrader 5 ya consolida estos timeframes. Así que podemos modificar las cosas para utilizar este nuevo valor.

Volviendo al código, se puede observar que el proceso para reducir el rango ha sido bastante simplificado. Esto se puede hacer en una sola línea y utilizando constantes. Esto es más que suficiente, ya que ahora podremos usar solo 7 bits para transmitir el valor. Sin embargo, debemos pensar un poco en cómo se hará. Recordemos que no nos interesa el timeframe en sí, sino simplemente saber si ha cambiado o no.


Comenzamos a transferir la información al servicio

Para transferir la información del timeframe al servicio y detectar si ha cambiado, necesitaremos usar el buffer del indicador. No hay otra forma de hacerlo. Bueno, técnicamente hay otras maneras, pero no quiero utilizarlas. Prefiero utilizar el buffer del indicador para esta tarea. De este modo, todo es invisible para el usuario y, al mismo tiempo, se consigue un cierto nivel de encapsulamiento de la información transferida.

Pero antes de continuar, integremos el último código probado en nuestro sistema. Se añadirá como una definición, por lo que el archivo de cabecera Defines.mqh se modificará para incluir la versión que se muestra a continuación:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay         "RePlay"
14. #define def_MaxPosSlider         400
15. #define def_MaskTimeService      0xFED00000
16. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. //+------------------------------------------------------------------+
18. union uCast_Double
19. {
20.    double    dValue;
21.    long      _long;                                 // 1 Information
22.    datetime _datetime;                              // 1 Information
23.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
24.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
25.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
26. };
27. //+------------------------------------------------------------------+
28. enum EnumEvents    {
29.          evHideMouse,               //Hide mouse price line
30.          evShowMouse,               //Show mouse price line
31.          evHideBarTime,             //Hide bar time
32.          evShowBarTime,             //Show bar time
33.          evHideDailyVar,            //Hide daily variation
34.          evShowDailyVar,            //Show daily variation
35.          evHidePriceVar,            //Hide instantaneous variation
36.          evShowPriceVar,            //Show instantaneous variation
37.          evCtrlReplayInit           //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Código fuente del archivo Defines.mqh

Muy bien. Ahora, observa lo que aparece en la línea 16. Se trata del código que utilizamos en las pruebas del tema anterior. Genial. Esto garantiza que estamos utilizando un código ya probado y que funciona perfectamente. Ahora viene la parte complicada, al menos si no has seguido esta secuencia de artículos o si acabas de llegar.

El buffer de datos del indicador siempre es del tipo double, es decir, tiene 8 bytes de longitud. Por motivos que ya expliqué en artículos anteriores de esta misma serie, cualquier información que queramos transferir debe caber dentro de estos 8 bytes. No podemos darnos el lujo de necesitar más de 8 bytes para transmitir información del indicador al servicio. Todo debe caber en esos 8 bytes. Bien, ahora reflexionemos. Perfecto. El indicador de mouse se puede usar en un mercado real. Es decir, podemos utilizarlo en un símbolo que reciba datos del servidor de trading en tiempo real. Sin embargo, el problema que estamos resolviendo ocurre al utilizar el servicio de repetición/simulación. Hasta ahora, el único indicador realmente necesario para la repetición/simulación es el indicador de control. Por lo tanto, es razonable observar cómo está configurado el buffer de datos del indicador de control. Podemos ver esto en la siguiente imagen:

Imagen 3

Entendamos algo: QWORD es una definición procedente de Assembly. Indica que estamos trabajando con un valor de 64 bits. El byte de la derecha es el cero, y el de la izquierda, el siete. En este caso, el indicador de control utiliza actualmente 4 bytes. Para comprenderlo, observa el fragmento de código del archivo de cabecera C_Controls.mqh que se muestra a continuación:

168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             if (rates_total > 0)
176.                Buff[rates_total - 1] = info.dValue;
177.          }
178. //+------------------------------------------------------------------+

Fragmento del archivo C_Controls.mqh

Observa que en la línea 171 se declara la unión, que también aparece en el archivo Defines.mqh mencionado anteriormente. Observa que estamos utilizando dos valores de matriz, cada uno de los cuales emplea 16 bits. Esto nos da un total de 32 bits. Aunque no todos los bits se utilizan completamente, asumiremos que se utilizan todos. Por lo tanto, cuatro de los ocho bytes disponibles ya están ocupados. Sin embargo, como nuestro sistema de modelado ha permitido pasar el timeframe en un byte, utilizaremos cinco de los ocho bytes disponibles. El mayor cuidado que debemos tener es con el índice que utilizaremos. Este tipo de detalle suele confundir mucho a quienes se inician en la programación, ya que tienden a olvidar que, al usar una unión, algunos bytes ya están ocupados con información útil. Si utilizas el índice equivocado, podrías sobrescribir información y obtener valores incorrectos. Así que volvamos a la imagen del QWORD. En ella, marcamos cuatro bytes, comenzando desde el índice cero. Esto significa que los índices del uno al tres están ocupados y no deben utilizarse para ningún otro propósito. Por lo tanto, el primer índice libre es el cuatro.

De este modo, añadimos una nueva entrada al archivo de cabecera Defines.mqh, que ahora quedará como se muestra a continuación:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay         "RePlay"
14. #define def_MaxPosSlider         400
15. #define def_MaskTimeService      0xFED00000
16. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame       4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.    double    dValue;
22.    long      _long;                                 // 1 Information
23.    datetime _datetime;                              // 1 Information
24.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents    {
30.          evHideMouse,               //Hide mouse price line
31.          evShowMouse,               //Show mouse price line
32.          evHideBarTime,             //Hide bar time
33.          evShowBarTime,             //Show bar time
34.          evHideDailyVar,            //Hide daily variation
35.          evShowDailyVar,            //Show daily variation
36.          evHidePriceVar,            //Hide instantaneous variation
37.          evShowPriceVar,            //Show instantaneous variation
38.          evCtrlReplayInit           //Initialize replay control
39.                   };
40. //+------------------------------------------------------------------+

Código fuente del archivo Defines.mqh

Observa que en la línea 17 tenemos una nueva definición que nos permitirá acceder de forma más segura al índice correcto. Muy bien, ya contamos con la información necesaria. Pero como el proceso es un poco más complicado de lo que imaginas, primero veamos cómo debería quedar el código del archivo de cabecera C_Controls.mqh. A continuación, se muestra el código completo:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonCtrl        def_PathBMP + "Ctrl.bmp"
011. #define def_ButtonCtrlBlock   def_PathBMP + "Ctrl_Block.bmp"
012. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
013. #resource "\\" + def_ButtonPlay
014. #resource "\\" + def_ButtonPause
015. #resource "\\" + def_ButtonCtrl
016. #resource "\\" + def_ButtonCtrlBlock
017. #resource "\\" + def_ButtonPin
018. //+------------------------------------------------------------------+
019. #define def_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
020. #define def_PosXObjects         120
021. //+------------------------------------------------------------------+
022. #define def_SizeButtons         32
023. #define def_ColorFilter         0xFF00FF
024. //+------------------------------------------------------------------+
025. #include "..\Auxiliar\C_Mouse.mqh"
026. //+------------------------------------------------------------------+
027. class C_Controls : private C_Terminal
028. {
029.    protected:
030.    private   :
031. //+------------------------------------------------------------------+
032.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
033.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
034. //+------------------------------------------------------------------+
035.       struct st_00
036.       {
037.          string   szBarSlider,
038.                   szBarSliderBlock;
039.          ushort   Minimal;
040.       }m_Slider;
041.       struct st_01
042.       {
043.          C_DrawImage *Btn;
044.          bool         state;
045.          short        x, y, w, h;
046.       }m_Section[eObjectControl::eNull];
047.       C_Mouse   *m_MousePtr;
048. //+------------------------------------------------------------------+
049. inline void CreteBarSlider(short x, short size)
050.          {
051.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
052.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
053.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
054.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
055.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
056.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
060.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
065.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
066.          }
067. //+------------------------------------------------------------------+
068.       void SetPlay(bool state)
069.          {
070.             if (m_Section[ePlay].Btn == NULL)
071.                m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
072.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start");
073.             if (!state) CreateCtrlSlider();
074.          }
075. //+------------------------------------------------------------------+
076.       void CreateCtrlSlider(void)
077.          {
078.             if (m_Section[ePin].Btn != NULL) return;
079.             CreteBarSlider(77, 436);
080.             m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock);
081.             m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true);
082.             m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL);
083.             PositionPinSlider(m_Slider.Minimal);
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveCtrlSlider(void)
087.          {         
088.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
089.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
090.             {
091.                delete m_Section[c0].Btn;
092.                m_Section[c0].Btn = NULL;
093.             }
094.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
095.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
096.          }
097. //+------------------------------------------------------------------+
098. inline void PositionPinSlider(ushort p)
099.          {
100.             int iL, iR;
101.             string szMsg;
102.             
103.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
104.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
105.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
106.             m_Section[ePin].x += def_PosXObjects;
107.              m_Section[ePin].x += 95 - (def_SizeButtons / 2);
108.              for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)   if (m_Section[c0].Btn != NULL)
109.              {
110.                 switch (c0)
111.                 {
112.                    case eLeft  : szMsg = "Previous Position";            break;
113.                    case eRight : szMsg = "Next Position";                break;
114.                    case ePin   : szMsg = "Go To: " + IntegerToString(p); break;
115.                    default     : szMsg = "\n";
116.                 }
117.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg);
118.             }
119. 
120.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
121.          }
122. //+------------------------------------------------------------------+
123. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
124.          {
125.             C_Mouse::st_Mouse InfoMouse;
126.             
127.             InfoMouse = (*m_MousePtr).GetInfoMouse();
128.             x = (short) InfoMouse.Position.X_Graphics;
129.             y = (short) InfoMouse.Position.Y_Graphics;
130.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
131.             {   
132.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
133.                   return c0;
134.             }
135.             
136.             return eNull;
137.          }
138. //+------------------------------------------------------------------+
139.    public   :
140. //+------------------------------------------------------------------+
141.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
142.          :C_Terminal(Arg0),
143.           m_MousePtr(MousePtr)
144.          {
145.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError >= ERR_USER_ERROR_FIRST) return;
147.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
148.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
149.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
150.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
151.             {
152.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
153.                m_Section[c0].y = 25;
154.                m_Section[c0].Btn = NULL;
155.             }
156.             m_Section[ePlay].x = def_PosXObjects;
157.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
158.             m_Section[eRight].x = m_Section[ePlay].x + 511;
159.             m_Slider.Minimal = eTriState;
160.          }
161. //+------------------------------------------------------------------+
162.       ~C_Controls()
163.          {
164.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
165.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
166.             delete m_MousePtr;
167.          }
168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             info._8b[def_IndexTimeFrame] = (uchar) def_IndicatorTimeFrame;
176.             if (rates_total > 0)
177.                Buff[rates_total - 1] = info.dValue;
178.          }
179. //+------------------------------------------------------------------+
180.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
181.          {
182.             short x, y;
183.             static ushort iPinPosX = 0;
184.             static short six = -1, sps;
185.             uCast_Double info;
186.             
187.             switch (id)
188.             {
189.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
190.                   info.dValue = dparam;
191.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
192.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
193.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
194.                   break;
195.                case CHARTEVENT_OBJECT_DELETE:
196.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
197.                   {
198.                      if (sparam == def_ObjectCtrlName(ePlay))
199.                      {
200.                         delete m_Section[ePlay].Btn;
201.                         m_Section[ePlay].Btn = NULL;
202.                         SetPlay(m_Section[ePlay].state);
203.                      }else
204.                      {
205.                         RemoveCtrlSlider();
206.                         CreateCtrlSlider();
207.                      }
208.                   }
209.                   break;
210.                case CHARTEVENT_MOUSE_MOVE:
211.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
212.                   {
213.                      case ePlay:
214.                         SetPlay(!m_Section[ePlay].state);
215.                         if (m_Section[ePlay].state)
216.                         {
217.                            RemoveCtrlSlider();
218.                            m_Slider.Minimal = iPinPosX;
219.                         }else CreateCtrlSlider();
220.                         break;
221.                      case eLeft:
222.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
223.                         break;
224.                      case eRight:
225.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
226.                         break;
227.                      case ePin:
228.                         if (six == -1)
229.                         {
230.                            six = x;
231.                            sps = (short)iPinPosX;
232.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
233.                         }
234.                         iPinPosX = sps + x - six;
235.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
236.                         break;
237.                   }else if (six > 0)
238.                   {
239.                      six = -1;
240.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
241.                   }
242.                   break;
243.             }
244.             ChartRedraw(GetInfoTerminal().ID);
245.          }
246. //+------------------------------------------------------------------+
247. };
248. //+------------------------------------------------------------------+
249. #undef def_PosXObjects
250. #undef def_ButtonPlay
251. #undef def_ButtonPause
252. #undef def_ButtonCtrl
253. #undef def_ButtonCtrlBlock
254. #undef def_ButtonPin
255. #undef def_PathBMP
256. //+------------------------------------------------------------------+

Código fuente del archivo C_Controls.mqh

Debido a algunas modificaciones realizadas en artículos anteriores, es necesario que realices ciertos cambios en el archivo de cabecera C_Controls.mqh, además de otras modificaciones que mencionaré a continuación. Observa la línea 10: esta línea hace referencia a la imagen Ctrl.bmp. ¿De qué imagen se trata? Se trata de la antigua imagen LEFT.BMP. De manera similar, la línea 11 hace referencia a la imagen LEFT_BLOCK.BMP. Ahora usaremos un nuevo nombre para estas imágenes. De todas formas, adjuntarás las imágenes para que, en caso de dudas, puedas incluirlas en el proyecto.

Existe otro cambio en el constructor de la clase C_Controls. Observa en la línea 146 que ahora el constructor solo retornará antes de completar su trabajo si el error indicado es uno de los que nosotros, como programadores, hemos definido en el código. Otros tipos de errores se ignorarán, al menos por ahora.

Sin embargo, el punto que realmente nos importa está en la línea 175, donde indicamos qué y dónde se añadirá al buffer del indicador, como se ha propuesto a lo largo de este artículo. Pero dado que utilizar esta información es un poco más complejo que simplemente colocarla en el buffer del indicador, no mostraré todo el código ahora. Esto se debe a que es necesario explicar cómo funcionan realmente las cosas a quienes están intentando aprender a programar en MetaTrader 5. Por lo tanto, estimado lector, si ya tienes más experiencia, te pido disculpas por no mostrar todo el código en este momento.


Consideraciones finales

Como será necesario explicar algunos cambios que se realizarán en el código del servicio y en el del indicador para que este sistema propuesto funcione, no incluiré dichos códigos en este artículo. Lamento esto para aquellos que tienen más experiencia en MQL5. Sin embargo, durante el tiempo que llevo publicando estos artículos, he notado que muchos lectores son principiantes en MQL5 y aspiran a programar en este lenguaje. Por esta razón, he procurado orientarles y explicar las cosas específicamente para ellos. No obstante, independientemente de esto, en el anexo encontrarás dos imágenes. Se trata de las imágenes referenciadas en el archivo de cabecera C_Controls.mqh.

Si deseas continuar siguiéndolo, no olvides realizar este ajuste en el proyecto.

Para el próximo artículo, quedarán pendientes las siguientes tareas: mostrar y explicar las modificaciones realizadas en el código fuente del indicador de control. En este caso, hemos visto los cambios en el archivo de cabecera, pero aún es necesario modificar el código del indicador. Además, se mostrará cómo ajustar y modificar el código fuente del archivo de cabecera C_Replay.mqh. Sin estos cambios y explicaciones, el servicio no podrá detectar que el timeframe ha sido modificado únicamente observando el indicador en el gráfico.

Hasta el próximo artículo. No olvides estudiar cuidadosamente el contenido presentado aquí.

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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Desarrollo de un sistema de repetición (Parte 73): Una comunicación inusual (II) Desarrollo de un sistema de repetición (Parte 73): Una comunicación inusual (II)
En este artículo, veremos cómo transferir información en tiempo real entre el indicador y el servicio, y comprenderemos por qué pueden surgir problemas al modificar el timeframe y cómo resolverlos correctamente. Como bono, tendrás acceso a la última versión de la aplicación de repetición/simulador. El contenido es exclusivamente didáctico y no debe utilizarse con otros fines.
Desarrollamos un asesor experto multidivisa (Parte 15): Preparamos el asesor experto para el trading real Desarrollamos un asesor experto multidivisa (Parte 15): Preparamos el asesor experto para el trading real
Al acercarnos gradualmente un asesor experto listo, debemos prestar atención a las cuestiones que son secundarias en la etapa de prueba de la estrategia comercial, pero que se vuelven importantes al pasar a la negociación real.
Visualización de transacciones en un gráfico (Parte 2): Visualización gráfica de datos Visualización de transacciones en un gráfico (Parte 2): Visualización gráfica de datos
Aquí vamos a desarrollar un script desde cero que simplifica la descarga de pantallas de impresión de operaciones para analizar las entradas de operaciones. Toda la información necesaria sobre una operación debe mostrarse cómodamente en un gráfico con la posibilidad de dibujar distintos plazos.
La teoría del caos en el trading (Parte 1): Introducción, aplicación a los mercados financieros e indicador de Lyapunov La teoría del caos en el trading (Parte 1): Introducción, aplicación a los mercados financieros e indicador de Lyapunov
¿Puede aplicarse la teoría del caos a los mercados financieros? En este artículo analizaremos en qué se diferencian la teoría clásica del caos y los sistemas caóticos del concepto propuesto por Bill Williams.