English Русский 中文 Deutsch 日本語 Português
preview
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

MetaTrader 5Ejemplos | 21 agosto 2023, 08:19
177 0
Daniel Jose
Daniel Jose

Introducción

En el artículo Desarrollo de un sistema de repetición — Simulación de mercado (Parte 07): Primeras mejoras (II), hicimos algunas correcciones y ajustes. Sin embargo, seguía habiendo un fallo, como demuestra el vídeo adjunto a ese artículo.

En este texto abordaremos la corrección de esta falla. Aunque a primera vista pueda parecer sencillo, veremos que se requieren varios pasos. El proceso será intrigante y curioso. Nuestro objetivo es garantizar que un indicador se aplique exclusivamente a un gráfico específico y a un activo determinado. Aunque el usuario insista, no podrá aplicar el indicador a otro gráfico ni abrirlo más de una vez en la misma sesión.

Te invito a seguir leyendo, ya que el contenido promete ser bastante enriquecedor.


Bloqueando el indicador en un activo concreto.

El primer paso será vincular el indicador de control al activo utilizado para la repetición del mercado. Este paso, aunque parezca sencillo, es fundamental para el desarrollo de nuestra tarea principal. Veamos cómo será el código del indicador en este contexto:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <Market Replay\C_Controls.mqh>
//+------------------------------------------------------------------+
C_Controls      Control;
//+------------------------------------------------------------------+
int OnInit()
{
        u_Interprocess Info;

        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if (_Symbol != def_SymbolReplay)
        {
                ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                return INIT_FAILED;
        }
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Control.DispatchMessage(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        switch (reason)
        {
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        if (_Symbol != def_SymbolReplay) break;
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        break;
        }
}
//+------------------------------------------------------------------+

Inicialmente, comprobamos si el activo en cuestión es el utilizado para la repetición del mercado. Si no lo es, el indicador se cerrará automáticamente. Ten en cuenta que es esencial conocer el nombre del indicador. Por lo tanto, la primera rutina que se ejecuta en la inicialización da nombre a nuestro indicador, lo que permite eliminarlo sin complicaciones.

Ahora, un detalle crucial: al eliminarlo del gráfico, MetaTrader 5 genera un evento DeInit. Este evento dispara la función OnDeInit, bajo la condición de un evento REASON_REMOVE, indicando que el indicador está siendo eliminado del gráfico. Esto se debe a que el activo no coincide con el que el indicador fue diseñado para utilizar. Si no realizamos una nueva comprobación y evitamos que se ejecute el código, se cerrará el gráfico del activo. Sin embargo, gracias a nuestra verificación, permanecerá abierto.

No se sorprendan si el código del indicador es diferente del presentado en el artículo anterior. En el texto anterior, la atención se centraba en otras mejoras y correcciones. Sin embargo, al terminar el artículo y el código, y al grabar el vídeo que acompaña a ese artículo, me di cuenta de que, aunque había solucionado uno de los problemas, otro había pasado desapercibido. Por lo tanto, fue necesario cambiar el código.

A pesar de los cambios, no detallaré aquí todas las modificaciones realizadas. Hubo que suprimir una parte importante, porque no era eficaz para aplicar el bloqueo que se abordará aquí. Por lo tanto, el código mencionado difiere mucho del anterior. Aún así, creo que el aprendizaje compartido en el artículo anterior puede ser útil para alguien en algún momento. Mantuve el artículo para mostrar que nadie es infalible, pero debemos esforzarnos por hacer las cosas de la manera correcta.

Con esto, hemos establecido el primer paso del bloqueo, asegurando que el indicador de control sólo existe en el gráfico del activo de repetición del mercado. Sin embargo, esta medida no impide que se añada más de un indicador, ya sea en el mismo gráfico o en gráficos diferentes, y esto debe ajustarse.


Debemos evitar varios indicadores en el mismo gráfico

Una vez resuelto uno de los problemas, abordemos otro. Este presenta soluciones variadas, dependiendo de lo que realmente quieras y estés dispuesto a hacer. Personalmente, no veo una solución ideal y definitiva para esta cuestión. Sin embargo, intentaré presentar un enfoque con el que puedas familiarizarte y comprender. Lo más importante es que la solución se basará exclusivamente en MQL5. Incluso he considerado el uso de codificación externa, pero he optado por utilizar sólo MQL5. Por muy tentador que hubiera sido recurrir a la codificación externa y utilizar una DLL para realizar el bloqueo, no hubiera supuesto un reto tan grande.

Creo que tenemos mucho que explorar en MQL5 antes de recurrir a una DLL externa para llenar los vacíos que el lenguaje MQL5 no aborda. Esto proporcionaría una solución que parece más "limpia" cuando se utiliza código externo. Sin embargo, recurrir a esta estrategia no demostraría un conocimiento profundo de MQL5. Además,  podría reforzar la percepción errónea de que MetaTrader 5 es una plataforma limitada. La falta de comprensión y la infrautilización de la plataforma alimentan esta falsa noción.

Para aplicar la solución propuesta, tendremos que hacer algunos cambios y deshacer otros. El primer paso concreto es modificar el archivo de cabecera InterProcess.mqh para que tenga la siguiente estructura:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableReplay        "Replay Infos"
#define def_GlobalVariableIdGraphics    "Replay ID"
#define def_SymbolReplay                "RePlay"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market Replay"
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;
                long    IdGraphic;
        }u_Value;
        struct st_0
        {
                bool    isPlay;
                int     iPosShift;
        }s_Infos;
};
//+------------------------------------------------------------------+

Para quienes no estén familiarizados con la programación, esto puede parecer algo extraño. Sin embargo, por sorprendente que parezca, la estructura anterior sólo utiliza 8 bytes de memoria. Puede que hayas notado que se ha eliminado una variable de la estructura del artículo anterior. El motivo es que ya no utilizaremos ese método de bloqueo. Adoptaremos un enfoque diferente, ligeramente más complejo pero significativamente más efectivo, para restringir el indicador de control a un único gráfico. Esto será muy específico y determinado por el servicio de repetición.

NOTA: Sería interesante que los desarrolladores de la plataforma MetaTrader 5 y del lenguaje MQL5 proporcionaran al servicio la capacidad de añadir un indicador a un gráfico específico o permitieran a un servicio llamar y ejecutar un script en un gráfico. Usando scripts, podemos añadir un indicador a un gráfico específico, pero con un servicio, esto no es factible. Aunque podemos abrir un gráfico, no podemos añadirle un indicador. Incluso utilizando funciones MQL5, siempre se informa de algún error al intentar realizar esta acción.  En el momento de escribir estas líneas, MetaTrader 5 se encuentra en la versión 5.00 build 3280.

Una nota relevante: En la etapa actual de la escritura de artículos, que se encuentra en una etapa más avanzada, yo era capaz de lograr tal hazaña. Sin embargo, cuando este artículo estaba siendo escrito, no pude encontrar ninguna referencia que pudiera ayudar con este tema. Por lo tanto, sigue esta serie en la repetición/simulador para entender cómo encontré la solución.

En este contexto, ejecutando el script de abajo, podrás abrir y añadir un indicador al gráfico:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart() 
{ 
  
        ENUM_TIMEFRAMES time = PERIOD_D1;
        string szSymbol = "EURUSD";
        long id = ChartOpen(szSymbol, time);
        ChartRedraw(id);

        ChartIndicatorAdd(id, 0, iCustom(szSymbol, time, "Media Movel.ex5"));
}

Sin embargo, si conviertes este mismo script en un servicio, no obtendrás el mismo resultado.

#property service
//+------------------------------------------------------------------+
void OnStart()
{
        ENUM_TIMEFRAMES time = PERIOD_D1;
        string szSymbol = "EURUSD";
        long id = ChartOpen(szSymbol, time);
        ChartRedraw(id);

        ChartIndicatorAdd(id, 0, iCustom(szSymbol, time, "Media Movel.ex5"));
}

Observe que el único cambio aquí es la propiedad de compilación, que ahora indica que el código compilado será un servicio. La simple acción de convertir un script en un servicio, utilizando una palabra reservada, cambia completamente la forma en que funciona el código, incluso si realiza la misma tarea que antes. Por lo tanto, necesitaremos utilizar una plantilla para añadir un indicador al gráfico. Si fuera factible añadir un indicador a través de un servicio, podríamos compilar el indicador como un recurso interno del servicio. De esta forma, al abrir el gráfico, éste recibiría el indicador directamente del servicio, sin necesidad de mezclarlo con otros indicadores.

Aunque impidas, como se muestra más arriba, añadir el indicador a un gráfico que no esté vinculado al activo de la repetición, el usuario puede insertar el indicador en un gráfico cuyo activo sea la repetición del mercado. Esto no puede permitirse. Así que, después de los cambios en el archivo de cabecera Interprocess.mqh, vamos a centrarnos en el código de servicio, más concretamente en el archivo de cabecera C_Replay.mqh.

En resumen, esto es lo que haremos: Indicaremos al indicador si el servicio está activo o no. Cuando esté activo, indicaremos cuál es el gráfico principal. Para ello, tenemos que ajustar el código, que ahora se verá como se describe a continuación:

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        u_Interprocess info;
                        
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                {
                        ChartClose(m_IdReplay);
                        ChartRedraw();
                }
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        GlobalVariableDel(def_GlobalVariableIdGraphics);
        GlobalVariableTemp(def_GlobalVariableIdGraphics);
        GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
        return m_IdReplay;
}

Lo que hicimos fue, inicialmente, borrar la variable global terminal. Luego volvemos a crear esta misma variable, asegurando ahora su carácter temporal.  Registramos en esta variable el valor del ID del gráfico abierto por el servicio. Con esto, ya hemos simplificado enormemente nuestro trabajo sobre el indicador, ya que lo único que tendremos que hacer es analizar este valor registrado por el servicio.

Sin embargo, no hay que olvidar que cuando terminemos el servicio, también tendremos que eliminar esta variable global adicional que hemos creado, tal y como se indica en el código a continuación:

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

Con estas modificaciones, podemos introducir un nuevo control en el indicador.

int OnInit()
{
        u_Interprocess Info;

        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics)))
        {
                ChartIndicatorDelete(ChartID(), 0, def_ShortName);
                return INIT_FAILED;
        }
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
}

Aunque intentemos añadir el indicador de control al activo de repetición de mercado, no podremos hacerlo a menos que el servicio de repetición haya creado la variable global terminal. Sólo con la presencia de esta variable se podrá insertar el indicador, incluso en el gráfico del activo de repetición. Sin embargo, esta acción todavía no resuelve nuestro problema. Necesitamos realizar algunas comprobaciones más.

A continuación, vamos a implementar una prueba que comenzará a vincular el indicador con el gráfico apropiado. El primer paso se presenta a continuación:

int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(ChartID(), 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;

        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != ChartID()) macro_INIT_FAILED;
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}

Debido a la frecuente repetición del mismo código en el procedimiento de inicialización, opto por definir una macro de ayuda. Así se evitan posibles errores u omisiones de escritura. Pasemos ahora al primer paso del bloqueo. Cuando se crea el gráfico de activos, el ID de este gráfico se transmite a través de la variable terminal global. Así, podemos capturar este valor para confirmar que el indicador de control se encuentra efectivamente en el gráfico esperado,  que debería ser el creado por el servicio de repetición. Si se intenta añadir el indicador a un gráfico distinto del generado por el servicio de repetición, esta acción será denegada.

Aunque esto resuelve una gran variedad de situaciones, todavía nos enfrentamos a un reto: la posibilidad de añadir indicadores de control adicionales en el mismo gráfico. Para resolver definitivamente este problema, adoptaremos un enfoque un tanto atípico. Ten en cuenta que, en el momento de escribir estas líneas, la plataforma se encuentra en la versión indicada en la imagen inferior:



Es posible que en el momento en que estés leyendo esto, la plataforma haya sufrido actualizaciones significativas, haciendo que el mecanismo aquí presentado quede obsoleto. Pero veamos el mecanismo que permite fijar el indicador de control para que no se puedan añadir nuevos indicadores de control en el mismo gráfico, restringiéndolo a un gráfico concreto. Si se elimina el indicador, se cerrará el gráfico y también se terminará el servicio de repetición.

Antes de pasar al código real, es importante destacar que el mecanismo que voy a utilizar se basa en la lógica booleana. Si no estás familiarizado con estos conceptos, te recomiendo una búsqueda sobre el tema, ya que son fundamentales para cualquier código a crear y desarrollar. Muchos podrían pensar que la solución más sencilla sería utilizar una DLL para gestionar la situación de forma más directa. En parte estoy de acuerdo, pero ¿dónde estaría el reto de crear una solución en un programa externo?

Esto no nos proporcionaría una comprensión completa y precisa de los límites del lenguaje MQL5 y cuáles serían las sugerencias para mejorarlo. Muchos afirman que C/C++ es el lenguaje más potente, y es cierto. Sin embargo, no nació completo; evolucionó y adquirió nuevas capacidades a medida que los desarrolladores lo exploraban hasta sus límites. Cuando se alcanzaban esos límites y aún no era posible implementar la funcionalidad deseada, se creaban nuevas funciones que hacían factibles proyectos antes inalcanzables. Así es como C/C++ se ha consolidado como un lenguaje tan robusto, capaz de abarcar prácticamente cualquier proyecto.

Soy consciente de que MQL5 tiene cualidades y potencial similares a C/C++. Todo se reduce a explorar y probar el lenguaje MQL5 hasta sus límites. Luego, cuando se alcancen estos límites, los desarrolladores podrán sugerir mejoras y nuevas características para MQL5. Con el tiempo, puede convertirse en un lenguaje extremadamente potente para el desarrollo de aplicaciones para MetaTrader 5.

Para entender lo que vamos a hacer en realidad, es necesario comprender una limitación actual del lenguaje, en el que la abstracción de cierta información no es factible. Atención a este punto: NO HE DICHO que sea imposible hacerlo, sino que no podemos crear una abstracción que nos facilite el trabajo. Son conceptos diferentes. Una cosa es desarrollar la abstracción de datos e información y otra es poder o no manipular los datos de la forma deseada. No hay que mezclar los conceptos.

Pues bien, en C/C++, tenemos la capacidad de crear una abstracción de datos tal que podemos aislar un bit específico dentro de una secuencia de bits. Esto se consigue de una forma bastante sencilla, como en el ejemplo:

union u01
{
        
double  value;
        struct st
        {
                ulong info : 63;
                bool signal;
        }c;
}data;

Aunque parezca curioso y trivial, estamos estableciendo una forma de abstracción de datos que nos permite identificar e incluso cambiar el signo de la información. Es cierto que este código, en el contexto actual, puede no ser de mucha utilidad. Pero consideremos una perspectiva diferente. Supongamos que queremos transmitir información y que utilizamos bits para controlar algo. Podríamos tener algo similar a lo que se muestra a continuación:

struct st
{
        bool PlayPause;
        bool Reservad : 6;
        bool RS_Info;
}ctrl;

En este escenario, el nivel de abstracción nos ayudaría a aislar los bits a los que realmente queremos acceder, simplificando la lectura del código. Sin embargo, como se ha mencionado, en la etapa actual de MQL5, no tenemos la capacidad de utilizar el nivel de abstracción presentado. Tenemos que adoptar un enfoque distinto; la abstracción pura no es factible. Pero si entendemos los límites del lenguaje, todavía podemos manipular los datos. Así que recurrimos a la lógica booleana para manejar datos que, de otro modo, se manejarían con abstracción. Sin embargo, el uso de la lógica booleana hace que el programa sea más complejo de interpretar. Pasamos de un sistema de alto nivel con abstracciones a un sistema de bajo nivel en el que la abstracción se reduce a la lógica booleana.

En el futuro, volveré sobre esta discusión. Pero se darán cuenta de que la razón estará más razonada que la presentada ahora. Todo lo que estoy mencionando puede parecer irrelevante, pero cuando mires el código final del indicador de control, entenderás lo que estoy tratando de ilustrar. A menudo, no es que MQL5 sea incapaz de realizar ciertas tareas. En realidad, muchos programadores son reacios a entrar en un nivel más profundo donde la abstracción de datos simplemente no existe, haciendo ciertas implementaciones inalcanzable.

A continuación se muestra el código completo e integral del indicador de control en la etapa actual de desarrollo. En él, logramos bloquear el indicador en un gráfico y evitar que el usuario agregue otros indicadores de control en la misma sesión de MetaTrader 5.

#property copyright "Daniel Jose"
#property description "This indicator cannot be used\noutside of the market replay service."
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <Market Replay\C_Controls.mqh>
//+------------------------------------------------------------------+
C_Controls      Control;
//+------------------------------------------------------------------+
#define def_BitShift ((sizeof(ulong) * 8) - 1)
//+------------------------------------------------------------------+
int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(id, 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;
        long id = ChartID();
        ulong ul = 1;

        ul <<= def_BitShift;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != id) macro_INIT_FAILED;
        if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device");
        Info.u_Value.IdGraphic |= ul;
        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Control.DispatchMessage(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        u_Interprocess Info;
        ulong ul = 1;

        switch (reason)
        {
                case REASON_CHARTCHANGE:
                        ul <<= def_BitShift;
                        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
                        Info.u_Value.IdGraphic ^= ul;
                        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value);
                        break;
                case REASON_REMOVE:
                case REASON_CHARTCLOSE:
                        if (_Symbol != def_SymbolReplay) break;
                        GlobalVariableDel(def_GlobalVariableReplay);
                        ChartClose(ChartID());
                        Control.Finish();
                        break;
        }
}
//+------------------------------------------------------------------+

¿Tienes problemas para entender lo que realmente está pasando? ¿Cómo funciona el indicador de control como he explicado? Bueno, ese es exactamente el punto. En este código no hay ninguna abstracción que facilite su comprensión de una forma sencilla y directa. Esto se debe a que MQL5 no nos permite alcanzar el mismo nivel de abstracción que conseguimos con C/C++. Por lo tanto, nos vemos obligados a recurrir a la lógica booleana, que aunque es más compleja de entender, nos permite manipular datos y conseguir los mismos resultados que obtendríamos con la abstracción.

Si te fijas, te darás cuenta de que un tipo double necesita 8 bytes para almacenar su valor. Del mismo modo, un tipo long (con signo) o ulong (sin signo) también ocupa los mismos 8 bytes. Teniendo en cuenta que el ID del gráfico, obtenido a través de ChartID, nos devuelve un tipo long, nos sobra 1 bit, que es precisamente el que se utiliza para indicar el signo. Lo que hacemos es utilizar exactamente este bit para fijar el indicador en el gráfico. Y manipulamos el nombre del indicador para evitar que se añada otro indicador de control al mismo gráfico. ¿Cómo? Vamos a explicarlo.

En primer lugar, definimos cuántos bits vamos a tener y con cuáles vamos a trabajar. Así que si estamos utilizando un sistema de 64 bits, 32 bits o 128 bits, esta definición utilizará el tipo y la longitud adecuados. Aunque sabemos que es de 64 bits, no quiero que la configuración sea estática, sino adaptable. Así que restamos 1 a este valor, aislando así el bit de signo del tipo long.

A continuación, activamos el bit menos significativo de estos 64 bits. Al hacerlo, obtenemos el valor 1, que será nuestro punto de partida. A continuación, realizamos un desplazamiento de 63 bits hacia la izquierda, dando como resultado el valor 0x800000000000000000000000, donde el bit más significativo tiene un valor igual a 1, es decir, es verdadero. Este paso podría evitarse si fijásemos este valor directamente, pero el riesgo de equivocarse al introducirlo es alto. Así que, procediendo de esta manera, minimizamos esta posibilidad.

Con este valor en la mano, tenemos dos opciones. La primera es bloquear el sistema. La segunda es desbloquear el sistema, permitiendo que la plataforma MetaTrader 5 vuelva a aplicar el indicador al gráfico cuando sea necesario. Dado que la segunda opción es más simple, vamos a empezar con ella.

Para desbloquear el sistema, tomamos el valor de la variable global del terminal, que contiene el ID del gráfico, y realizamos una operación XOR sobre este valor, de forma que se mantengan todos los bits excepto el más significativo. Idealmente, en lugar de una operación XOR, deberíamos realizar una operación NOT seguida de una operación AND. Esto eliminaría cualquier información contenida en el bit más significativo. Sin embargo, como esta operación sólo se produce cuando ese bit ya tiene alguna información, no veo ningún problema en utilizar la operación XOR. Si encuentras problemas, sustituye la operación XOR por la línea sugerida a continuación:

Info.u_Value.IdGraphic &= (~ul);

De cualquier forma, hemos conseguido nuestro objetivo, que es poner a cero el bit más significativo. Entonces, podemos almacenar el valor de nuevo en la variable global del terminal antes de que MetaTrader 5 intente poner el indicador de control de nuevo en el gráfico. Con esto terminamos la parte más sencilla del sistema de bloqueo. Ahora, pasemos a la parte más compleja.

En este paso, lo primero que hay que hacer es comprobar que el activo del gráfico coincide con el activo utilizado en la repetición y que el servicio de repetición está en funcionamiento. Si alguna de estas condiciones no se cumple, el indicador será eliminado del gráfico. A continuación, capturamos el valor contenido en la variable global del terminal, buscando el dato que indica el ID del gráfico creado por el servicio de repetición. Comparamos este valor con el de la ventana del gráfico. Si son diferentes, se elimina también el indicador. Posteriormente, tomamos el valor obtenido y lo desplazamos 63 bits a la derecha, comprobando si este bit está activo. Si lo está, se elimina de nuevo el indicador.

Aunque esto puede parecer suficiente, aún queda un problema por resolver. Este problema, en particular, me dio algo de trabajo que hacer manteniendo todo dentro de MQL5. Digo esto porque era necesario añadir una línea al código. Sin ella, cada vez que traté de bloquear el sistema de la adición de un indicador en el gráfico, se añadió de todos modos. Incluso si no causaba interferencias, permanecía visible en la ventana de indicadores, molestándome. Fue entonces cuando tuve la idea de cambiar el nombre del indicador, pero de forma que sólo el primer indicador recibiera este nuevo nombre. Este indicador se genera junto con el gráfico, en el momento en que el servicio lo crea.

Cuando la plantilla se aplica al gráfico, el indicador se incluye automáticamente. Hablando de plantilla, queda un aspecto más por cubrir. Pero primero, concluyamos esta explicación. Por último, después de todos estos pasos, realizamos una operación OR y almacenamos el resultado en la variable global del terminal, para bloquear el indicador de control. Para concluir este tema, hay que realizar un ajuste más. Todo lo realizado hasta ahora sería inútil y no funcionaría correctamente si no realizara esta última modificación. Podría omitir esta información, enorgulleciéndome de haber conseguido algo que muchos consideran imposible. Si alguien intentara replicarlo, quizá no lo conseguiría, y no estoy aquí para presumir o sugerir que soy insustituible. No busco tales reconocimientos. Lo que quiero es demostrar que es posible ir más allá de lo que muchos consideran factible, y que a veces la solución a un problema puede estar en lugares inesperados.

Si sigues todas las instrucciones presentadas hasta ahora e intentas ejecutar el proceso, te darás cuenta de que tendrás éxito en casi todos los aspectos excepto en uno. Por mucho que lo intentes, no podrás evitar que se añadan nuevos indicadores de control al gráfico creado por el servicio de repetición Te preguntarás: "¿Pero cómo es posible? He seguido todos los pasos, incluso he añadido la línea específica para ello. ¿Me estás tomando el pelo?"

Me gustaría decir que es una broma, pero no lo es. De hecho, no podrás evitar esta situación. Se debe a un "fallo" (nótese las comillas) en el sistema. No estoy seguro de cuál es la causa ni de cómo puede ocurrir, pero fíjate en el siguiente código:

long ViewReplay(ENUM_TIMEFRAMES arg1)
{
        u_Interprocess info;
                                
        if ((m_IdReplay = ChartFirst()) > 0) do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                {
                        ChartClose(m_IdReplay);
                        ChartRedraw();
                }
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        GlobalVariableDel(def_GlobalVariableIdGraphics);
        GlobalVariableTemp(def_GlobalVariableIdGraphics);
        GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
        return m_IdReplay;
}

En el mismo instante en que se ejecuta esta línea, ocurre algo peculiar. Pasé mucho tiempo intentando averiguarlo hasta que decidí abrir la plantilla para entender cómo funciona. Fue entonces cuando me di cuenta de que forzando al sistema a no utilizar una plantilla, sino a crear y aplicar el indicador al gráfico automáticamente, tal y como expliqué al principio del artículo, el sistema funcionaba. Sin embargo, al utilizar la plantilla, fallaba.

Busqué y rebusqué profundamente. No encontré a nadie que tuviera la respuesta. Sin embargo, utilizando técnicas y habilidades adquiridas durante años de programación, identifiqué el problema. Si te fijas en los artículos anteriores y en los datos del archivo de plantilla, encontrarás la siguiente información:

<indicator>
name=Custom Indicator
path=Indicators\Market Replay.ex5
apply=0
show_data=1
scale_inherit=0
scale_line=0
scale_line_percent=50
scale_line_value=0.000000
scale_fix_min=0
scale_fix_min_val=0.000000
scale_fix_max=0
scale_fix_max_val=0.000000
expertmode=0
fixed_height=-1


A primera vista puede parecer que todo es correcto, y de hecho lo es.Sin embargo, cambiando algo en este fragmento anterior, el sistema empieza a funcionar como se esperaba, consiguiendo bloquear el indicador de control e impidiendo que se añadan otros.

La versión corregida se muestra a continuación:

<indicator>
name=Custom Indicator
path=Indicators\Market Replay.ex5
apply=1
show_data=1
scale_inherit=0
scale_line=0
scale_line_percent=50
scale_line_value=0.000000
scale_fix_min=0
scale_fix_min_val=0.000000
scale_fix_max=0
scale_fix_max_val=0.000000
expertmode=0
fixed_height=-1

He preferido no resaltar exactamente lo que ha cambiado; quiero que lo veas y trates de entenderlo. Pero ten por seguro que en la versión adjunta del código, el sistema ya funcionará correctamente.


Conclusión

Como he mencionado antes, podría haber omitido esta información y cuando alguien me preguntara por qué no conseguía que el sistema funcionara, podría actuar como si fuera el mejor programador que jamás ha existido. Sin embargo, no busco el vanagloriarme. Quiero que más gente aprenda, entienda y se sienta motivada para buscar soluciones. Y, siempre que sea posible, que compartan sus conocimientos, porque así es como promovemos la evolución. Ocultar conocimientos no es signo de superioridad, sino de miedo o inseguridad, hasta el punto de no aceptar críticas ni reconocer que otros pueden aprender de ti e incluso superarte.

Ya he superado esa fase. Por eso comparto cómo hacer las cosas. Espero que muchos se sientan inspirados para hacer lo mismo. Un fuerte abrazo a todos y nos vemos en el próximo artículo. Nuestro trabajo no ha hecho más que empezar.




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

Archivos adjuntos |
Market_Replay.zip (13058.5 KB)
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.
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)
En el artículo anterior realizamos correcciones en algunos puntos y agregamos pruebas a nuestro sistema de repetición para garantizar la mayor estabilidad posible. Asimismo, comenzamos a crear y utilizar un archivo de configuración para dicho sistema.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 10): Sólo datos reales para la repetición Desarrollo de un sistema de repetición — Simulación de mercado (Parte 10): Sólo datos reales para la repetición
Aquí veremos cómo se pueden utilizar datos más fiables (ticks negociados) en el sistema de repetición, sin tener que preocuparnos necesariamente de si están ajustados o no.
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.