English Русский 中文 Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 07): Primeras mejoras (II)

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 07): Primeras mejoras (II)

MetaTrader 5Ejemplos | 21 agosto 2023, 08:14
230 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 06): Primeras mejoras (I), corregimos algunos puntos y añadimos algunas pruebas a nuestro sistema de repetición. Estas pruebas tienen como objetivo asegurar la máxima estabilidad posible, y al mismo tiempo, empezamos a crear y utilizar un archivo de configuración para el sistema.

A pesar de todo lo que hemos hecho y de todos los esfuerzos para crear un sistema de repetición extremadamente intuitivo y estable, todavía enfrentamos algunos problemas pendientes. Algunos de ellos son más simples de resolver, otros son más complejos.

Si viste el video que está al final del artículo anterior, habrás notado algunas fallas en el sistema que aún necesitan ser mejoradas. Decidí dejar estas fallas visibles para que todos sean conscientes de que todavía hay mucho por mejorar y corregir antes de empezar a usarlo de forma más intensiva. Esto no significa que el sistema no pueda ser utilizado para practicar.

Sin embargo, no quiero agregar nuevas funcionalidades antes de haber corregido todos los detalles que puedan causar alguna inestabilidad en el sistema o en la plataforma. Imagino que algunos deseen usar y practicar en la repetición mientras el mercado esté abierto, esperando que aparezca una operación real o utilizando un EA automático en la plataforma.

No obstante, si el sistema de repetición no está suficientemente estable, no sería aconsejable mantenerlo funcionando mientras el mercado esté abierto, en caso de que haya la posibilidad de que ocurra una operación real. El motivo de esta advertencia es que el sistema de repetición podría causar fallas o impedir que otras funciones de la plataforma funcionen adecuadamente.

Entonces, continuando nuestro esfuerzo para maximizar la estabilidad y usabilidad de la repetición de mercado, vamos a implementar algunos recursos adicionales en el código para mejorar aún más el sistema.


Asegurando que el indicador de control permanezca en el gráfico

La primera mejora que implementaremos es en nuestro indicador de control. Actualmente, cuando se inicia la repetición, se carga una plantilla. Este archivo de plantilla debería contener un indicador utilizado para controlar el servicio de repetición. Hasta aquí todo bien, si este indicador no está presente, el servicio de repetición no podrá ser manipulado. Por lo tanto, este indicador es extremadamente importante para el servicio.

Sin embargo, una vez cargado, hasta ahora no había una manera de garantizar que este indicador permaneciera en el gráfico. A partir de ahora, eso cambiará, vamos a asegurarnos de que permanezca en el gráfico y, si por alguna razón es retirado, el servicio de repetición deberá ser cerrado inmediatamente.

¿Pero cómo podemos hacer eso? ¿Cómo asegurarnos de que un indicador permanezca en el gráfico o verificar si realmente está ahí? Hay algunas formas de hacerlo. Pero la manera que, en mi opinión, es la más sencilla y al mismo tiempo la más elegante, es usar la propia plataforma MetaTrader 5 para hacer esa verificación, y esto utilizando el lenguaje MQL5.

Generalmente, un indicador no contiene ciertos eventos. Pero hay uno que es especialmente útil para nosotros, el evento DeInit

Este evento siempre es llamado cuando algo sucede y el indicador necesita ser cerrado para luego ser iniciado inmediatamente por el evento OnInit. Por lo tanto, ¿cuando la función OnDeInit sea llamada, podemos informar al servicio de repetición que el indicador ha sido removido? Sí, podemos, pero hay un detalle en esta historia. El evento OnDeInit no solo es llamado cuando el indicador es removido o el gráfico es cerrado.

También se activa cuando cambias el período del gráfico o modificas los parámetros del indicador. Por lo tanto, aparentemente, la situación parece complicarse nuevamente. Sin embargo, al consultar la documentación del evento OnDeInit, encontrarás algo que podemos y vamos a utilizar: los códigos de razón de desinicialización.

Al examinar estos códigos, notamos que hay dos que serán muy útiles para indicar que el indicador de control ha sido removido del gráfico. Por lo tanto, creamos el siguiente código:

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        break;
        }
}

Lo que estamos haciendo aquí es verificar si la razón por la que se ha disparado el evento DeInit y, en consecuencia, la función OnDeInit, no es la remoción del indicador del gráfico. Si ese es el caso, debemos informar al servicio de repetición que el indicador ya no está presente en el gráfico, y que el servicio debe ser cerrado de inmediato.

La forma de hacerlo es eliminando la variable global del terminal, que actúa como un puente entre el indicador de control y el servicio de repetición. Tan pronto como esta variable sea eliminada, el servicio de repetición interpretará que las actividades han terminado y se cerrará.

Si observas el código del servicio, notarás que al cerrar el servicio, también se cierra el gráfico del activo de repetición. Esto se hace exactamente cuando el servicio ejecuta el siguiente código:

void CloseReplay(void)
{
        ArrayFree(m_Ticks.Info);
        ChartClose(m_IdReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
}

Es decir, si el indicador de control es removido, el gráfico se cerrará junto con el indicador, ya que el servicio de repetición forzará este cierre. Sin embargo, puede ocurrir que el servicio no pueda cerrar el gráfico del activo de repetición de mercado. En ese caso, necesitamos asegurarnos de que este gráfico se cerrará, incluso si el activo permanece en la ventana de observación del mercado y, por alguna razón, el servicio de repetición no puede eliminarlo a pesar de intentar hacerlo.

Para hacer esto, vamos a agregar otra línea a la función OnDeInit del indicador de control.

void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}

Lo que sucede ahora es lo siguiente: tan pronto como el indicador de control sea removido del gráfico, por cualquier motivo, incluso si el servicio de repetición no puede eliminar el gráfico, ya habrá habido un intento de cerrarlo por el propio indicador. Esto puede parecer un poco irracional, pero quiero que en caso de algún fallo o error por parte del usuario, el gráfico, al igual que el servicio de repetición, libere la plataforma y no cause molestias.

Con esta implementación, al menos tenemos la garantía de que, si el indicador de control deja de estar presente en el gráfico, el sistema será cerrado y el gráfico será cerrado. Pero también tenemos otro problema que involucra al indicador.


Evitando que el Indicador de control sea destruido

Esta es una cuestión bastante seria, ya que puede ocurrir que el indicador permanezca en el gráfico, pero los elementos que forman parte de él simplemente sean removidos o destruidos, lo que impide que el indicador pueda ser utilizado correctamente.

Afortunadamente, esta es una situación bastante sencilla de solucionar y corregir. Sin embargo, es algo que requiere cuidado para que no se convierta en una fuente de problemas en el futuro. Esto se debe a que al evitar que el indicador sea destruido o que sus elementos y objetos sean removidos, podríamos crear un monstruo incontrolable que nos cause muchos problemas. Para resolver esto, vamos a capturar y manejar el evento de destrucción de objetos en el gráfico. Pero veamos cómo se hace en la práctica.

Para empezar, en la clase C_Control, vamos a agregar la siguiente línea de código:

void Init(const bool state = false)
{
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(m_id, state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
}

Al agregar esta línea de código, estamos solicitando a MetaTrader 5 que nos envíe un evento cuando se remueva un objeto gráfico de la pantalla. El simple hecho de hacer esto no nos garantiza que los objetos no sean removidos, pero sí nos asegura que la plataforma MetaTrader 5 nos informará cuando eso suceda.

Para garantizar que cuando la clase C_Control sea removida, los objetos también lo sean, necesitamos decirle a MetaTrader 5 cuándo no enviar el evento de remoción de objetos. Uno de los puntos que utiliza este tipo de función se muestra a continuación:

~C_Controls()
{
        m_id = (m_id > 0 ? m_id : ChartID());
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_PrefixObjectName);
}

Lo que estamos haciendo es decirle a MetaTrader 5 que NO queremos que nos envíe un evento cuando se elimine un objeto del gráfico. De esta manera, podremos eliminar los objetos deseados sin más problemas.

Sin embargo, no todo es sencillo, y aquí tenemos un problema potencial. Observa el fragmento a continuación:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;
        static int six = -1, sps;
        int x, y, px1, px2;
                
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                {
                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");
                                        m_Slider.szBtnPin = NULL;
                                }
                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                ChartRedraw();
                        }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                break;

// ... Restante do código ....

Esta línea tendrá que eliminar la barra de control de posición, lo que activará el evento de eliminación de objetos.

Puedes pensar que simplemente podemos desactivar y, después de haber eliminado la barra de control, reactivar el evento. Esto es cierto, pero piensa en lo siguiente: a medida que el código crece, estas acciones de activar y desactivar se vuelven mucho más comunes de lo que pueden parecer inicialmente. Además, hay otro punto: los objetos deben colocarse en un orden específico para que se representen correctamente.

Por lo tanto, simplemente activar y desactivar el evento de eliminación no garantizará un manejo adecuado del evento. Necesitamos crear una solución más elegante y sostenible, que mantenga los objetos en el orden correcto para que su presentación siempre sea la misma, de manera que el usuario no note ninguna diferencia en el sistema de posicionamiento.

La solución más sencilla es crear un procedimiento que desactive el evento de eliminación, elimine los objetos que forman parte de la misma cadena y luego reactive el evento de eliminación. Esto se logra fácilmente con el siguiente código, que realizará esta tarea en la barra de control.

inline void RemoveCtrlSlider(void)
{                       
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(m_id, def_NameObjectsSlider);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
}

Ahora, cada vez que necesitemos eliminar solo la barra de control, llamaremos a este procedimiento y obtendremos el resultado deseado.

Aunque parezca algo trivial, este procedimiento, en el nivel actual de desarrollo, es utilizado no solo una, sino dos veces en la misma función, como se puede ver a continuación:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
        {
                u_Interprocess Info;
                static int six = -1, sps;
                int x, y, px1, px2;
                        
                switch (id)
                {
                        case CHARTEVENT_OBJECT_DELETE:
                                if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
                                {
                                        if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
                                        {
                                                RemoveCtrlSlider();
                                                CreteCtrlSlider();
                                        }else
                                        {
                                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                                CreateBtnPlayPause(Info.s_Infos.isPlay);
                                        }
                                        ChartRedraw();
                                }
                                break;
                        case CHARTEVENT_OBJECT_CLICK:
                                if (sparam == m_szBtnPlay)
                                {
                                        Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                        if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                                        {
                                                RemoveCtrlSlider();
                                                m_Slider.szBtnPin = NULL;
                                        }
                                        Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                                        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                        ChartRedraw();
                                }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
                                else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                   
                                break;
                        case CHARTEVENT_MOUSE_MOVE:
                                x = (int)lparam;
                                y = (int)dparam;
                                px1 = m_Slider.posPinSlider + def_MinPosXPin - 14;
                                px2 = m_Slider.posPinSlider + def_MinPosXPin + 14;
                                if ((((uint)sparam & 0x01) == 1) && (m_Slider.szBtnPin != NULL))
                                {
                                        if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
                                        {
                                                six = x;
                                                sps = m_Slider.posPinSlider;
                                                ChartSetInteger(m_id, CHART_MOUSE_SCROLL, false);
                                        }
                                        if (six > 0) PositionPinSlider(sps + x - six);
                                }else if (six > 0)
                                {
                                        six = -1;
                                        ChartSetInteger(m_id, CHART_MOUSE_SCROLL, true);
                                }
                                break;
                }
        }

Pero vamos a enfocarnos un poco más en la parte responsable de manejar los eventos de eliminación de objetos. Cuando le informamos a la plataforma MetaTrader 5 que deseamos recibir eventos cuando un objeto gráfico es eliminado del gráfico, ella genera un evento de eliminación por cada objeto removido. Luego capturamos este evento y podemos verificar qué objeto fue eliminado. 

Un detalle, no verás cuál es el objeto que será eliminado, sino cuál fue el objeto que realmente fue eliminado. En nuestro caso, estamos probando para verificar si es uno de los usados por el indicador de control. Si esto es cierto, haremos una nueva prueba para verificar si fue uno de los objetos de la barra de control o si fue el botón de control. Si fue uno de los objetos que forman parte de la barra de control, la barra será completamente destruida y recreada inmediatamente. 

No necesitamos informar nada a esta rutina de creación, ya que ella hace todo el trabajo. Ahora, si se trata del botón de control, tenemos una situación diferente. En este caso, debemos leer la variable global del terminal para conocer cuál es el estado actual del botón y solo entonces, podemos solicitar su creación.

Para finalizar, forzamos una colocación inmediata de todos los objetos en el gráfico, de esta forma el usuario ni siquiera notará que fueron eliminados.

Básicamente, esto es lo que haremos para que todo quede en su lugar. Pero ahora vamos a ver otra cosa que también es importante para que el sistema de repetición pueda funcionar.


Solamente un gráfico de repetición, por favor

Una de las cosas que más ocurren cuando trabajamos con un sistema que abre gráficos automáticamente es que comienza a abrir gráficos del mismo activo, de manera que, después de un tiempo, no sabes exactamente con qué estás lidiando.

Para evitar esto, implementé una pequeña prueba que resuelve exactamente este problema, que es el hecho de que el sistema de repetición continúa abriendo un gráfico tras otro, todos solo sobre repetición. La existencia de esta rutina también nos garantiza cierta estabilidad en relación a los valores contenidos en la variable global del terminal.

Si tienes varios gráficos reflejando la misma idea, en este caso, la repetición de mercado, puede ser que en uno de ellos tengamos un valor creado por el indicador de control, mientras que en otro tengamos un valor completamente diferente. Aunque esto aún no ha sido totalmente resuelto, el simple hecho de que ya no tendremos varios gráficos refiriéndose al mismo activo, al mismo tiempo, será de gran ayuda.

La manera de garantizar que solo tendremos un único gráfico abierto de un determinado activo se muestra a continuación:

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay) ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

Lo que estamos haciendo aquí es verificar si existe algún gráfico abierto en el terminal de la plataforma MetaTrader 5. En caso de que haya alguno, lo usamos como punto inicial para verificar cuál es el símbolo que está abierto. Si el símbolo es el activo utilizado como repetición de mercado, entonces cerraremos este gráfico.

Aquí hay un tema, si el gráfico del símbolo de repetición está abierto, tenemos dos alternativas: la primera es cerrar el gráfico, que es exactamente lo que estamos haciendo. La otra es finalizar el ciclo, pero en este segundo caso, puede ocurrir que haya más de un gráfico abierto del mismo activo. Por eso, prefiero cerrar todos los gráficos abiertos, y lo haremos hasta que se haya verificado el último gráfico. Al final, no tendremos ningún gráfico del repetición de mercado abierto.

Entonces, necesitaremos abrir un gráfico que contendrá la repetición de mercado, aplicar la plantilla de forma que el indicador de control pueda ser utilizado, forzar una representación gráfica y devolver el índice del gráfico que se ha abierto.

A pesar de haber hecho este tipo de cosas, nada impide que el usuario abra nuevos gráficos del activo de repetición después de que el sistema ya esté cargado. Podríamos agregar una prueba adicional en el servicio para que solo se mantenga abierto un gráfico durante todo el período en que se ejecute la repetición. Sin embargo, sé que hay operadores que les gusta utilizar más de un gráfico del mismo activo al mismo tiempo. En cada uno de estos gráficos, usarían un intervalo de tiempo diferente.

Por esta razón, no añadiré esa prueba adicional, pero haremos algo un poco diferente. No permitiremos que el indicador de control esté presente y se ejecute en ningún otro gráfico que no sea el que ha sido abierto por el servicio. A pesar de eso, en un primer momento, podrías intentar finalizar el indicador en el gráfico original e intentar colocarlo en otro gráfico. Pero al hacerlo, el gráfico se cerrará y el servicio se detendrá, impidiendo así el cambio.

El trabajo de garantizar que el indicador de control no pueda ser abierto en otro gráfico que no sea el original se ve en el próximo tema.


Solo un indicador de control por sesión

Esta parte es bastante interesante y puede ayudarte en algunos casos. Entonces, veamos cómo garantizar que un indicador pertenezca solo a un gráfico, en una sesión de trabajo de MetaTrader 5.

Para entender cómo hacerlo, observa el siguiente código:

int OnInit()
{
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}

Este código verificará si existe una variable global del terminal. En caso de que esa variable exista, la capturaremos para usarla más adelante. En caso de que no exista, la inicializaremos.

Ahora, existe un detalle: el procedimiento OnInit es llamado siempre que algo sucede, ya sea en el gráfico o en la actualización de los parámetros del indicador. En el caso actual, el indicador no contiene y no recibirá ningún parámetro. Así que solo nos quedamos con los eventos del gráfico, y estos ocurrirán cada vez que cambies el intervalo de tiempo del gráfico, es decir, si pasas de 5 minutos a 4 minutos, esto hará que se llame a OnInit. En este caso, si simplemente bloqueamos la inicialización del indicador, si la variable global del terminal está presente, tendrás un problema. El motivo es que el gráfico se cerrará y, en consecuencia, también se cerrará el servicio. Complicado, ¿verdad?

Pero la solución que vamos a utilizar será bastante simple y, al mismo tiempo, muy elegante. Utilizaremos precisamente la variable global del terminal, de manera que sabremos si ya existe o no un indicador de control en algún gráfico. Si existe, no podrá colocarse en otro gráfico hasta que no esté presente en ningún gráfico abierto en la sesión actual de MetaTrader 5.

Para hacerlo, lo primero que haremos será realizar una modificación en el código utilizado para la comunicación entre los procesos.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay "Replay Infos"
//+------------------------------------------------------------------+
#define def_MaxPosSlider        400
//+------------------------------------------------------------------+
union u_Interprocess
{
        double Value;
        struct st_0
        {
                bool    isPlay;         // Indica se estamos no modo Play ou Pause ...
                bool    IsUsing;        // Indica se o indicador está ou não presente ...
                int     iPosShift;      // Valor entre 0 e 400 ...
        }s_Infos;
};

Recuerda que podemos agregar variables internas a la estructura, siempre y cuando no exceda el límite de 8 bytes, que es justamente el tamaño que ocupa una variable double en memoria. Pero como el tipo booleano solo utilizará 1 bit para existir, y aún tenemos 7 bits libres dentro del byte que está usando la variable isPlay, podemos agregar fácilmente otros 7 booleanos sin problemas. Así que utilizaremos uno de esos 7 bits libres para saber si el indicador de control está presente o no en algún gráfico.

NOTA: Aunque este mecanismo es adecuado, hay un problema aquí. Pero no hablaré de eso ahora. Eso se verá en otro artículo en el futuro, cuando sea necesario hacer un cambio en esta estructura vista ahora.

Muy bien, entonces ya debes estar pensando que con solo hacer esto, todo estará correcto. Pero en realidad, tenemos que agregar algunas cosas al código. Sin embargo, no nos preocuparemos por el código del servicio, solo modificaremos el código del indicador para que esta variable agregada sea realmente útil para nosotros.

Entonces, lo primero que debemos hacer es agregar algunas líneas extras en el código del indicador, comencemos con el punto que se muestra a continuación:

void Init(const bool state = false)
{
        u_Interprocess Info;
                                
        if (m_szBtnPlay != NULL) return;
        m_id = ChartID();
        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
        CreateBtnPlayPause(state);
        GlobalVariableTemp(def_GlobalVariableReplay);
        if (!state) CreteCtrlSlider();
        ChartRedraw();
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        Info.s_Infos.IsUsing = true;
        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
}

Aquí, informaremos y registraremos en la variable global del terminal que el indicador de control ha sido creado. Así que hacemos una llamada para que esto quede registrado, pero ¿por qué debemos tener una llamada anterior para crear la variable global del terminal? ¿No podríamos omitir esa llamada? En realidad, esta primera llamada sirve para informar a la plataforma MetaTrader 5 que la variable global es temporal y no debe mantenerse. Incluso si pides que la plataforma guarde los datos de las variables globales del terminal, estas variables consideradas temporales no tendrán sus valores guardados, se perderán.

Esto es lo que queremos, porque si necesitas guardar y luego restablecer las variables globales del terminal, no es adecuado que tengamos una variable informando que el indicador de control está presente cuando en realidad simplemente no existe. Por esta razón, tenemos que hacer las cosas de esta manera.

Ahora, es necesario tener cuidado con este punto. Ya que cuando la plataforma reponga el indicador en el gráfico, el valor de la variable global del terminal puede ser diferente, porque ya avanzamos en la repetición. Entonces, si esta línea no se coloca, el sistema simplemente iniciará la repetición de mercado desde cero.

El hecho de hacer esto ya nos ofrece una garantía, pero aún debemos hacer un cambio más.

case CHARTEVENT_OBJECT_CLICK:
        if (sparam == m_szBtnPlay)
        {
                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
                {
                        RemoveCtrlSlider();
                        m_Slider.szBtnPin = NULL;
                }
                Info.s_Infos.IsUsing = true;
                Info.s_Infos.iPosShift = m_Slider.posPinSlider;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                ChartRedraw();
        }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
        break;

Cada vez que hacemos clic en el botón de pausa/play, realizamos un cambio en el valor de la variable global del terminal. Sin embargo, de la forma en que estaba el código, si haces clic en este botón, el valor guardado ya no contendrá la indicación de que el indicador de control está presente en el gráfico. Debido a este detalle, necesitamos agregar una línea de código. Ahora tendremos la indicación correcta, ya que cambiar de pausa a play, o viceversa, ya no creará una falsa indicación.

La parte que involucra la clase C_Replay ya está concluida, pero aún tenemos un poco más de trabajo. El simple hecho de crear la indicación no garantiza nada más que la existencia de dicha indicación. Debemos ir al código del indicador en sí y ahora es necesario tener un poco más de cuidado para que las cosas funcionen adecuadamente y no se conviertan en algo con un comportamiento totalmente extraño.

Así que presta mucha atención a los detalles involucrados. Lo primero que debemos ver es el código de OnInit:

int OnInit()
{
#define def_ShortName "Market Replay"
        u_Interprocess Info;
        
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if (GlobalVariableCheck(def_GlobalVariableReplay))
        {
                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                if (Info.s_Infos.IsUsing)
                {
                        ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                        return INIT_FAILED;
                }
        } else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
#undef def_ShortName
}

Aquí, por razones prácticas, hemos creado una definición que nos proporciona el nombre del indicador. Este nombre será utilizado para que podamos eliminar el indicador de la lista de indicadores presentes en la ventana donde podemos ver qué indicadores están realmente en el gráfico. Incluso aquellos que no aparecen pueden ser visualizados en esta ventana. No queremos que ningún indicador inútil permanezca allí.

Por esta razón, eliminamos el indicador del gráfico y para saber si el indicador ya está presente en algún gráfico, probamos el valor que creamos en la variable global del terminal, de esta manera, la verificación es muy simple y eficiente. Hay otras formas de realizar esta misma prueba, pero como estamos usando una variable global del terminal, es más sencillo hacer esta verificación por medio de ella.

El resto de la función continúa de la misma forma, pero ahora ya no será posible agregar el indicador de control en más de un gráfico en la misma sesión del MetaTrader 5. Aquí, ni en el código que estará adjunto, se ha agregado ninguna advertencia de que el indicador ya está presente en otro gráfico, pero puedes añadir esta advertencia antes de que la rutina devuelva un error de inicialización.

Aunque puedas pensar que esto es suficiente, todavía tenemos otro punto que corregir. Debes recordar que cada vez que el MetaTrader 5 recibe una solicitud de cambio de marco de tiempo, tal vez este sea el evento más común en la plataforma. Todos los indicadores, al igual que otras cosas, serán eliminados para luego ser reinicializados.

Ahora, piensa un poco: Si el indicador está indicando, a través de la variable global del terminal, que existe una copia del mismo siendo ejecutada en algún gráfico, y cambias el marco de tiempo de este gráfico específico, el indicador será eliminado. Pero cuando la plataforma MetaTrader 5 reponga el indicador en el gráfico, este será impedido de ser colocado en el gráfico. El motivo es precisamente lo que se vio en el código de la rutina OnInit. Tenemos que de alguna manera modificar la variable global del terminal para que ya no informe que el indicador de control está presente.

Existen maneras bastante exóticas de hacer esto, pero nuevamente, la plataforma MetaTrader 5, junto con el lenguaje MQL5, nos ofrece un medio bastante simple para realizar esta tarea. Observa el código a continuación:

void OnDeinit(const int reason)
{
        u_Interprocess Info;
        
        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        if (GlobalVariableCheck(def_GlobalVariableReplay))
                        {
                                Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.IsUsing = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                        }
                        break;
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}

Recuerda lo siguiente: Cuando el indicador sea eliminado, se disparará un evento DeInit, y este llamará a la rutina OnDeInit. Esta rutina recibe en su parámetro un valor que indica la razón de la llamada. Este valor es exactamente lo que utilizaremos.

Estos valores pueden ser encontrados en Códigos de Motivos de Desinicialización. Allí, vemos que el REASON_CHARTCHANGE indicará que el período gráfico fue modificado. Entonces, lo que haremos es probar -siempre es bueno probar las cosas, nunca supongas, PRUEBA- si existe una variable global de terminal con el nombre esperado. Si esto es cierto, capturaremos el valor de la variable. Como el servicio puede estar en ejecución y no queremos interferir, aquí modificamos la información para indicar que el indicador de control ya no estará presente en el gráfico. Después de esto, registraremos la información nuevamente en la variable global de terminal.

Aquí, debo hacer una advertencia sobre una posible falla en este sistema. Aunque la probabilidad de que algo salga mal es baja, siempre debes ser consciente de que hay una falla en el método y estar preparado para posibles problemas.

El problema aquí es que entre la lectura y la escritura de la variable, habrá una pequeña brecha. Por más pequeña que sea, existe, donde el servicio puede escribir un valor en la variable global de terminal antes de que el indicador lo haga. Si ocurre este tipo de evento, el valor esperado por el servicio al acceder a la variable global de terminal será diferente de lo que realmente está en la variable.

Existen formas de sortear esta falla, pero aquí, en este sistema que trata de la repetición de mercado, no es algo crítico, podemos ignorar esta falla y dejarla pasar sin preocupaciones. Pero, si deseas utilizar este mismo mecanismo en algo más complejo, donde los valores almacenados son críticos, te aconsejo que busques más sobre los medios para bloquear y desbloquear la lectura y escritura de memoria compartida, ya que la variable global de terminal no es más que eso, una memoria compartida.

En el video a continuación, podrás entender un poco de lo que ha sido corregido y todavía necesita ser corregido. Observa que las cosas están empezando a ponerse realmente serias.




Conclusión

A pesar de que el sistema descrito aquí parece ser el ideal para resolver fallos operacionales causados por el mal uso del indicador de control, todavía no es una solución realmente eficaz, ya que solo evita algunos de los tipos de problemas que podemos tener en realidad.

Creo que, al ver el vídeo, notarás que todavía tenemos otro problema por resolver, que, aunque pueda parecer simple, es mucho más complicado de lo que parece a primera vista.


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

Archivos adjuntos |
Market_Replay.zip (13057.92 KB)
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 08): Bloqueo del indicador Desarrollo de un sistema de repetición — Simulación de mercado (Parte 08): Bloqueo del indicador
En este artículo te mostraré cómo bloquear un indicador, simplemente utilizando el lenguaje MQL5, de una forma muy interesante y sorprendente.
Algoritmo de recompra: un modelo matemático para aumentar la eficiencia Algoritmo de recompra: un modelo matemático para aumentar la eficiencia
En este artículo, usaremos el algoritmo de recompra como guía en un mundo con una mayor comprensión de la efectividad de los sistemas comerciales y comenzaremos a trabajar en los principios generales para mejorar la eficiencia comercial usando las matemáticas y la lógica; también aplicaremos los métodos menos comunes para aumentar la eficiencia en el contexto del uso de cualquier sistema comercial.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 09): Eventos personalizados Desarrollo de un sistema de repetición — Simulación de mercado (Parte 09): Eventos personalizados
Aquí veremos cómo accionar eventos personalizados y mejorar la cuestión de cómo el indicador informa del estado del servicio de repetición/simulación.
Teoría de categorías en MQL5 (Parte 7): Dominios múltiples, relativos e indexados Teoría de categorías en MQL5 (Parte 7): Dominios múltiples, relativos e indexados
La teoría de categorías es un apartado diverso y en expansión de las matemáticas, que solo recientemente ha comenzado a ser trabajado por la comunidad MQL5. Esta serie de artículos tiene por objetivo repasar algunos de sus conceptos para crear una biblioteca abierta y seguir usando este maravilloso apartado en la creación de estrategias comerciales.