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

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 04): Haciendo ajustes (II)

MetaTrader 5Ejemplos | 6 julio 2023, 09:30
180 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollando un sistema de Replay - Simulación de mercado (Parte 03): Ajustando las cosas (I)", creamos un Expert Advisor (EA) que podía controlar de manera simple el servicio de repetición de mercado. Sin embargo, hasta ahora solo hemos logrado hacer una cosa: pausar o dar play al sistema. No se ha creado ningún tipo de control que nos permita seleccionar la posición inicial deseada para la repetición. Es decir, todavía no es posible comenzar la repetición desde la mitad del período o de otro punto específico. Siempre tenemos que empezar desde el principio de los datos, lo cual no es práctico para quienes desean hacer entrenamientos.

En este momento, vamos a implementar la selección del punto inicial de la repetición de la manera más simple posible. Al mismo tiempo, haremos un pequeño cambio de estrategia debido a la petición de algunos amigos que gustaron del sistema que estamos desarrollando y quisieran usar sus propios EAs. Entonces, haremos una alteración en el sistema para permitir esto.

Por ahora, este será el enfoque adoptado para mostrar cómo se crea realmente una nueva aplicación. Muchas personas piensan que surge de la nada, sin entender el proceso completo, desde la aparición de la idea hasta la estabilización total del sistema y del código, permitiendo que la aplicación ejecute exactamente lo que se espera.


Cambio del Expert Advisor (EA) por el Indicador

Este cambio es bastante simple de hacer. Una vez realizado, podrás usar tu propio EA para realizar estudios en el servicio de repetición de mercado o operar en el mercado real. Por ejemplo, puedes utilizar el EA que mostré en artículos anteriores. Para más detalles, revisa la serie "Desarrollando un EA comercial DESDE CERO". A pesar de no estar diseñado para ser 100% automatizado, puede ser adaptado para ser utilizado en el servicio de repetición. Pero eso es tema para el futuro. Además, también puedes utilizar algún EA de la serie "Cómo construir un EA que opere automáticamente (Parte 01): Conceptos y estructuras", donde expliqué cómo crear un EA que funciona de manera totalmente automática.

Sin embargo, en este momento, nuestro enfoque principal no es el uso de un EA. Vamos a explorar esto más en el futuro. Ahora, nuestro foco es otro.

Puedes ver el código completo del indicador a continuación. Incluye exactamente la funcionalidad que existía en el EA y la coloca en el indicador:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <Market Replay\C_Controls.mqh>
//+------------------------------------------------------------------+
C_Controls      Control;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");
        Control.Init();
        
        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);
}
//+------------------------------------------------------------------+

La única parte diferente es la adición del nombre corto, que preferiblemente debería incluirse en los indicadores. Esa es la parte resaltada en el código de arriba. Al hacer esto, obtenemos un beneficio adicional: podremos usar cualquier EA para practicar y realizar estudios en el servicio de repetición. Sin embargo, antes de que alguien cuestione, la repetición de mercado NO ES un comprobador de estrategias. Está orientado a personas que desean practicar la lectura del mercado y, así, ganar consistencia al mejorar su percepción de los movimientos de los activos. La repetición del mercado no reemplaza el muy buen comprobador de estrategias de MetaTrader 5. Sin embargo, el comprobador de estrategias no es adecuado para la práctica de la repetición de mercado.

Aunque la conversión no parezca tener efectos secundarios a primera vista, esto no es del todo cierto. Al ejecutar el sistema de repetición actualizado para ser controlado por un indicador en lugar de un EA, notarás un fallo. Cuando ocurre un cambio de período gráfico, el indicador es eliminado del gráfico y luego reinstalado. Esta operación de eliminación e instalación hace que el botón que indica si estamos en modo pausa o reproducción sea inconsistente con el estado real del servicio de repetición. Para corregir esto, necesitaremos hacer un pequeño ajuste. Con esto, el código de inicio del indicador será como se indica a continuación:

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;
}

Las adiciones destacadas en el código aseguran que el estado del servicio de repetición corresponda al botón que vemos en el gráfico. Los cambios en el código de control son bastante simples y no requieren un realce especial.

Ahora, el archivo de plantilla utilizado en el servicio de repetición, que antes usaba un EA, pasará a usar un indicador. Esto nos deja completamente libres para hacer otros cambios en el futuro.


Implementación del control de posición

Aquí vamos a implementar un control para indicar dónde queremos ir dentro del archivo de repetición, con el fin de iniciar la ejecución de algún estudio. Sin embargo, antes de que algunos piensen que tendremos total precisión sobre el punto que vamos a indicar, no lo haremos. Y el motivo no es la imposibilidad de hacer tal cosa. En realidad, sería más simple indicar un punto exacto. Sin embargo, al conversar e intercambiar experiencias con personas con más tiempo en el mercado, llegamos a un consenso. Lo ideal es no ir a un punto exacto donde tengamos un movimiento específico ya esperado, sino comenzar la repetición del mercado en un punto cercano al movimiento deseado. En otras palabras, necesitarás entender qué estaba pasando antes de entrar efectivamente en el movimiento.

Esta idea me pareció tan buena que decidí que la repetición del mercado no debería ir a un punto exacto. Aunque sería más fácil de implementar, debe ir a un punto cercano. Qué punto cercano depende de la cantidad de operaciones realizadas en el día. Cuantas más operaciones se ejecuten, más difícil será acertar el punto exacto.

Con esto, será más común que accedas a un punto cercano y tengas que entender qué estaba pasando para crear una simulación de operación real. De nuevo, NO estamos creando un comprobador de estrategias. Todo el trabajo en esta etapa se llevará a cabo dentro de la clase C_Control. Aunque después de un tiempo, tendremos un movimiento fuerte y direccional.

Todo el trabajo en este paso se hará dentro de la clase C_Control. ¡Así que manos a la obra!

Lo primero que haremos es definir algunas configuraciones.

#define def_ButtonLeft  "Images\\Market Replay\\Left.bmp"
#define def_ButtonRight "Images\\Market Replay\\Right.bmp"
#define def_ButtonPin   "Images\\Market Replay\\Pin.bmp"

Ahora necesitamos crear un conjunto de variables para almacenar los datos del sistema de posición. Se implementan de la siguiente manera:

struct st_00
{
        string  szBtnLeft,
                szBtnRight,
                szBtnPin,
                szBarSlider;
        int     posPinSlider,
                posY;
}m_Slider;

Eso es lo que acabas de notar. Vamos a usar un control deslizante para indicar una posición aproximada desde donde se debe iniciar la repetición. Por eso, ahora tenemos una función genérica que se usará tanto para crear los botones de play/pausa como para los botones de nuestro control deslizante. Esta función se muestra a continuación, y creo que no tendrás dificultades para entenderla, ya que es algo bastante común.

inline void CreateObjectBitMap(int x, int y, string szName, string Resource1, string Resource2 = NULL)
                        {
                                ObjectCreate(m_id, szName, OBJ_BITMAP_LABEL, 0, 0, 0);
                                ObjectSetInteger(m_id, szName, OBJPROP_XDISTANCE, x);
                                ObjectSetInteger(m_id, szName, OBJPROP_YDISTANCE, y);
                                ObjectSetString(m_id, szName, OBJPROP_BMPFILE, 0, "::" + Resource1);
                                ObjectSetString(m_id, szName, OBJPROP_BMPFILE, 1, "::" + (Resource2 == NULL ? Resource1 : Resource2));
                        }

Ahora, todos los botones utilizarán esta función para ser creados. Esto facilita mucho las cosas, aumenta la reutilización de código y hace las cosas más estables y rápidas. Lo próximo que se creará será una función que representará el canal a ser usado en el control deslizante. Esta barra es creada por la siguiente función:

inline void CreteBarSlider(int x, int size)
                        {
                                ObjectCreate(m_id, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XDISTANCE, x);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
                        }

La parte más interesante aquí es la representación de los bordes del canal de control. Puedes ajustarlo como desees, así como la anchura del canal, que se ajusta en la propiedad OBJPROP_YSIZE. Pero al modificar el valor de esta propiedad, no olvides ajustar también el valor sustraído de m_Slider.posY, para que el canal quede en medio de los botones.

La función que crea los botones de play/pausa ahora es la siguiente:

void CreateBtnPlayPause(long id, bool state)
{
        m_szBtnPlay = def_PrefixObjectName + "Play";
        CreateObjectBitMap(5, 25, m_szBtnPlay, def_ButtonPause, def_ButtonPlay);
        ObjectSetInteger(id, m_szBtnPlay, OBJPROP_STATE, state);
}

¡Mucho más simple, verdad? Ahora veamos la rutina que creará los controles deslizantes. Se puede ver a continuación:

void CreteCtrlSlider(void)
{
        u_Interprocess Info;
                                
        m_Slider.szBarSlider = def_PrefixObjectName + "Slider Bar";
        m_Slider.szBtnLeft   = def_PrefixObjectName + "Slider BtnL";
        m_Slider.szBtnRight  = def_PrefixObjectName + "Slider BtnR";
        m_Slider.szBtnPin    = def_PrefixObjectName + "Slider BtnP";
        m_Slider.posY = 40;
        CreteBarSlider(82, 436);
        CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft);
        CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight);
        CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
        ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;
        PositionPinSlider(Info.s_Infos.iPosShift);
}

Observa detenidamente los nombres de los controles, esto es importante. Estos controles no estarán disponibles mientras la repetición esté en modo play. Por lo tanto, cada vez que estemos en modo pausa, se llamará a esta rutina, creando los controles deslizantes para nosotros. Nota que, debido a esto, capturaremos el valor de la variable global del terminal para poder identificar y posicionar correctamente el control deslizante.

Por lo tanto, se recomienda que no toques la variable global del terminal manualmente. Un detalle importante a tener en cuenta es respecto al pin. A diferencia de los botones, se crea con el punto de anclaje exactamente en el centro, facilitando la ubicación. Aquí ya vemos una llamada a otro procedimiento, que se muestra a continuación:

inline void PositionPinSlider(int p)
{
        m_Slider.posPinSlider = (p < 0 ? 0 : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
        ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
        ChartRedraw();
}

Este procedimiento colocará el botón deslizante en una región determinada, asegurando que se mantenga dentro de los límites establecidos anteriormente.

Como puedes imaginar, aún necesitamos hacer algunos ajustes menores en el sistema. Con cada cambio en el período gráfico, el indicador se reiniciará, lo que podría hacernos perder el punto actual en el que estamos en el servicio de repetición. Una forma de evitar esto es hacer algunas adiciones a la rutina de inicio. Aprovechando estos cambios, también añadiremos algunas cosas extra. Entonces vamos a ver cómo quedó la rutina de inicio:

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

Ahora también añadimos un código para reenviar los eventos de movimiento del ratón al indicador. Sin esta adición, los eventos del ratón se perderían y no serían reenviados por MetaTrader 5 al indicador. Para evitar que el control deslizante sea visible cuando no sea necesario, añadimos una pequeña prueba. Si esta prueba confirma la necesidad de mostrar el control deslizante, se mostrará en la pantalla.

Con todo lo que hemos visto hasta ahora, puedes estar preguntándote: ¿cómo será el manejo de los eventos? ¿Tendremos un código extra súper complicado? Bueno, de alguna manera, la forma de manejar los eventos del ratón no cambia mucho. El hecho de que deseemos añadir un evento de arrastre no es algo muy complicado. Lo que realmente necesitas hacer es gestionar algunos límites para mantener las cosas bajo control. Además, la implementación en sí es bastante simple.

Echemos un vistazo al código de la función que maneja todos estos eventos: DispatchMessage. Pero para facilitar, vamos a ver el código en partes. Así será más fácil explicar lo que está sucediendo. El primer fragmento que veremos es responsable del manejo de los eventos de clic en los objetos. Mira el código a continuación:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;

//... outras variáveis locais ....
                                
        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 ...

Cuando hacemos clic en el botón de play/pausa, necesitamos hacer algunas cosas. Una de ellas es crear el control deslizante si estamos en modo de pausa. Por otro lado, si salimos del modo de pausa y entramos en modo de reproducción, los controles deben ser eliminados del gráfico para que ya no podamos acceder a ellos. Al mismo tiempo, reenviamos el valor actual del control deslizante a la variable global del terminal. De esta manera, el servicio de repetición tiene acceso a la posición porcentual en la que estamos o queremos colocar el sistema de repetición.

Además de estos problemas relacionados con el botón de play/pausa, también necesitamos lidiar con los eventos que ocurren cuando hacemos clic en los botones de desplazamiento puntual del control deslizante. Si hacemos clic en el botón de desplazamiento a la izquierda, el valor actual del control deslizante debe disminuir en una unidad. Del mismo modo, si hacemos clic en el botón de desplazamiento a la derecha, debe añadir una unidad al control hasta el límite máximo establecido.

Fíjate que es algo bastante simple. Al menos en esta parte, no es algo muy complicado para tratar los mensajes de clic en los objetos. Pero ahora viene una cuestión un poco más complicada, que es arrastrar el control deslizante. Para hacerlo, vamos a ver el código que maneja los eventos de movimiento del ratón. Este código se muestra 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)
        {

// ... EVENTO de clicar no objeto ...

                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;
        }
}

Esto parece un poco más complicado, pero en realidad es tan simple como tratar los clics en los objetos. La única gran diferencia es que ahora tendremos que usar más variables, y algunas de ellas deben ser estáticas para que el valor no se pierda entre las llamadas. Cuando ocurre un evento de movimiento del ratón, MetaTrader 5 envía un mensaje a nuestro sistema. Debemos y vamos a usar ese mensaje para averiguar qué ha pasado, ya sea para saber dónde está el puntero del ratón, qué botones están presionados o comprobar alguna otra cosa. Toda esta información es proporcionada por el mensaje enviado por MetaTrader 5 a nuestra aplicación.

Cuando se presione el botón izquierdo, tendremos algo que realmente deseamos utilizar. Sin embargo, para garantizar que el control deslizante esté en la pantalla y no tengamos un falso positivo, realizamos una prueba adicional para garantizar la integridad de lo que estamos haciendo.

Una vez que la prueba indica que este evento es válido, realizamos otra prueba para verificar si estamos haciendo clic en el botón deslizante, es decir, en la región perteneciente al control deslizante. Al mismo tiempo, verificamos si esta posición aún es válida, ya que puede suceder que el clic ya se haya realizado, pero la posición no sea válida. En este caso, debemos ignorarlo. Si esta prueba tiene éxito, almacenamos tanto la posición del clic como el valor del control. Además, necesitamos bloquear el arrastre del gráfico, lo que es necesario para el siguiente paso, donde calcularemos la posición del control deslizante basándonos en los valores anteriores presentes en el control. Almacenar estos datos antes de cualquier cálculo es extremadamente importante, ya que facilita el ajuste de las cosas y saber cómo actuar aquí. Pero de la manera en que se está haciendo, será algo súper simple y fácil de implementar, ya que el cálculo será solo un cálculo de desviación. 

Cuando se libere el botón izquierdo, la situación volverá a la normalidad. Es decir, el gráfico podrá ser arrastrado nuevamente, y la variable estática utilizada para almacenar la posición del ratón tendrá un valor que indica que no se está analizando ninguna posición. Este mismo método se puede utilizar para arrastrar cualquier cosa en el gráfico, lo cual es muy bueno. Y todo esto se hace utilizando el estilo de clic y arrastre. Entonces, todo lo que necesitas hacer es analizar dónde está la región que puede recibir el clic, hacer los ajustes necesarios y el resto será hecho por el código, que será similar al mostrado arriba.

Con todo esto funcionando, ya tenemos el comportamiento deseado para los controles. Pero aún no hemos terminado. Necesitamos hacer que el servicio utilice el valor que estamos indicando en el control deslizante, y esto se hará en el próximo tema.


Ajuste de la clase C_Replay

Las cosas nunca son exactamente como algunos imaginan. El simple hecho de haber creado un control deslizante y ajustado las cosas en la clase de control (C_Control) no significa que todo esté funcionando perfectamente. Necesitamos hacer algunos ajustes menores en la clase responsable de armar la repetición.

Estos ajustes no son complicados de hacer. De hecho, son muy pocos y en puntos muy específicos. Sin embargo, es importante tener en cuenta que cualquier cambio realizado en una clase afectará a la otra. Pero no siempre será necesario hacer cambios en otros puntos. La preferencia es evitar tocar puntos innecesarios y buscar el máximo encapsulamiento siempre que sea posible, ocultando así la complejidad de todo el sistema.

Vamos directo a lo que importa. Lo primero que se debe hacer, antes que nada, es ajustar la rutina Event_OnTime. Esta rutina es responsable de agregar los ticks negociados en el activo de repetición. Básicamente, vamos a agregar algo pequeño a esta rutina. Ve el código a continuación:

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
inline int Event_OnTime(void)
{
        bool isNew;
        int mili, test;
        static datetime _dt = 0;
        u_Interprocess Info;
                                
        if (m_ReplayCount >= m_ArrayCount) return -1;
        if (m_dt == 0)
        {
                m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                m_Rate[0].tick_volume = 0;
                m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                _dt = TimeLocal();
        }
        isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
        m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
        mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
        do
        {
                while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                {
                        m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                        m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                        m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                        m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                        m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                        isNew = false;
                        m_ReplayCount++;
                }
                mili++;
        }while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec);
        m_Rate[0].time = m_dt;
        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
        mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
        test = (int)((m_ReplayCount * def_MaxPosSlider) / m_ArrayCount);
        GlobalVariableGet(def_GlobalVariableReplay, Info.Value);
        if (Info.s_Infos.iPosShift != test)
        {
                Info.s_Infos.iPosShift = test;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
        }
                        
        return (mili < 0 ? 0 : mili);
};
#undef macroGetMin

En esta rutina, estamos construyendo las barras de 1 minuto. Observa que agregamos una variable en esta parte. Esta variable no existía en el código anterior. Ahora tendremos la representación de la posición porcentual relativa almacenada en la variable global del terminal. Por lo tanto, necesitamos esta variable para decodificar el contenido interno almacenado en la variable del terminal. Después de agregar el tick negociado a la barra de 1 minuto, necesitamos saber cuál es el porcentaje actual de la posición en la que se encuentra la repetición. Esto se hace en este cálculo, donde descubrimos la posición relativa en relación con la cantidad total de ticks almacenados.

Este valor se compara luego con el valor almacenado en la variable global del terminal. Si son diferentes, actualizamos el valor para que, cuando el sistema se pausa, ya indique la posición relativa correcta. De esta manera, no será necesario realizar cálculos extra o lidiar con molestias innecesarias.

Con esto, concluimos la primera etapa. Sin embargo, todavía tenemos otro problema que resolver. ¿Cómo posicionar el sistema de repetición en la posición relativa deseada después de ajustar el valor durante una pausa?

Este problema es un poco más complicado de resolver. Especialmente cuando se trata de agregar o restar barras en el sistema de repetición La resta es un poco más compleja de resolver, pero no es el gran problema en esta fase de desarrollo. Sin embargo, será un desafío en la próxima etapa, que se abordará en el próximo artículo de esta serie. Lo primero que hay que hacer es añadir una rutina extra en la clase C_Replay, para añadir o quitar barras del sistema de repetición. Veamos la preparación de esta rutina a continuación:

int AdjustPositionReplay()
{
        u_Interprocess Info;
        int test = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_ArrayCount);
                                
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == test) return 0;
        test = (int)(m_ArrayCount * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (test < m_ReplayCount)
        {
                CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
                CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
                m_ReplayCount = 0;
                m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                m_Rate[0].tick_volume = 0;
                m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
        };
        for (test = (test > 0 ? test - 1 : 0); m_ReplayCount < test; m_ReplayCount++)
                Event_OnTime();

        return Event_OnTime();
}

En el código anterior, podemos ver el fragmento que es la base de este sistema de ajuste. Vamos a entender lo que está sucediendo en este sistema base. Primero, generamos el valor porcentual de la posición actual. Luego, comparamos ese valor con el valor encontrado en la variable global del terminal. Este valor almacenado en la variable global es registrado por el sistema de control. Si los valores son iguales (no es el valor absoluto, sino el valor porcentual), la rutina se termina, ya que estamos en el punto correcto porcentualmente o el usuario no modificó la posición durante la pausa del sistema.

Sin embargo, si los valores son diferentes, se genera un valor absoluto basado en el valor porcentual indicado en la variable global del terminal. Es decir, ahora tendremos un punto absoluto desde el cual el sistema debe iniciar la repetición. Este valor difícilmente será igual al contador de ticks de negociación debido a una serie de factores. Si es menor que el valor actual en el contador de repetición, todo el contenido presente en el activo actual será eliminado.

Esta es la parte complicada, pero no en esta fase de desarrollo. Será en la próxima fase. Sin embargo, no hay motivo para preocuparse en este momento. Ahora, podemos hacer algo común a ambas situaciones: agregar nuevos valores hasta que la posición del contador de repetición corresponda a la posición absoluta menos 1. La resta de 1 tiene el propósito de devolver un valor que se utilizará como retraso posteriormente. Esto se logra utilizando la propia función Event_OnTime. 

Como todo cambio trae un poco de dificultad, vamos a ver lo que necesitó ser modificado en el sistema. Podemos ver esto al observar el código a continuación, que fue el único lugar que sufrió cambios:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user01 = "WINZ21_202110220900_202110221759"; //Arquivo com ticks
//+------------------------------------------------------------------+
C_Replay        Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        if (!Replay.CreateSymbolReplay(user01)) return;
        id = Replay.ViewReplay();
        Print("Aguardando permissão para iniciar replay ...");
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Serviço de replay iniciado ...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = (Replay.Event_OnTime() > 0);
                }else
                {
                        if (bTest)
                        {
                                delay = ((delay = Replay.AdjustPositionReplay()) == 0 ? 3 : delay);
                                bTest = false;
                                t1 = GetTickCount64();
                        }else if ((GetTickCount64() - t1) >= (uint)(delay))
                        {
                                if ((delay = Replay.Event_OnTime()) < 0) break;
                                t1 = GetTickCount64();
                        }
                }
        }
        Replay.CloseReplay();
        Print("Serviço de replay finalizado ...");
}
//+------------------------------------------------------------------+

Mientras estemos en modo de pausa, haremos esta prueba para verificar si estamos cambiando el estado del servicio. Cuando esto ocurra, solicitaremos a la clase C_Replay que realice un reposicionamiento, que podrá o no ser ejecutado.

Si se ejecuta el reposicionamiento, tendremos el valor del próximo retraso que se utilizará después de este ajuste y el sistema haya sido reposicionado. Si esto es necesario, continuaremos naturalmente por el resto del tiempo hasta que salgamos de la fase de reproducción y entremos en la fase de pausa. En este momento, todo el procedimiento se repetirá nuevamente.


Conclusión

En el video, se muestra el sistema completo en funcionamiento, allí podrás observar cómo sucede todo. Sin embargo, es importante notar que será necesario esperar a que todo se estabilice antes de usar el sistema de repetición. Durante el desplazamiento de la posición hasta el punto deseado, el movimiento puede parecer difícil de operar.

Esta situación será corregida en el futuro. Por ahora, podemos seguir adelante, ya que aún tenemos mucho por resolver.



En el archivo adjunto, estoy proporcionando dos archivos de ticks reales del mercado para que puedas experimentar con el sistema de movimiento y posicionamiento en días con diferentes volúmenes de negociación. Esto te permitirá ver cómo funciona el sistema de posicionamiento porcentual. Vale la pena señalar que esto hace las cosas más desafiantes para aquellos que desean estudiar un momento específico del mercado, pero esa es la intención, como se explicó al principio del artículo.

Con este sistema de repetición que estoy mostrando, realmente aprenderás a analizar el mercado. No habrá un punto exacto donde dirás: "AQUÍ... es aquí donde debo entrar". Puede ser que el movimiento que hayas observado esté a unas barras de distancia. Por lo tanto, tendrás que aprender a analizar el mercado o puede que no te guste este sistema de repetición presentado en esta serie de artículos.


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

Archivos adjuntos |
Market_Replay.zip (10795.89 KB)
Aprendiendo a diseñar un sistema de trading con Fibonacci Aprendiendo a diseñar un sistema de trading con Fibonacci
El presente artículo supone la continuación de la serie dedicada a la construcción de sistemas comerciales basados ​​en los indicadores más populares. La próxima herramienta técnica que analizaremos será el indicador de Fibonacci. Hoy veremos cómo escribir un programa basado en las señales de este indicador.
Algoritmos de optimización de la población: Algoritmo de siembra y crecimiento de árboles (Saplings Sowing and Growing up — SSG) Algoritmos de optimización de la población: Algoritmo de siembra y crecimiento de árboles (Saplings Sowing and Growing up — SSG)
El algoritmo de siembra y crecimiento de árboles (SSG) está inspirado en uno de los organismos más resistentes del planeta, que es un ejemplo notable de supervivencia en una amplia variedad de condiciones.
Indicadores basados ​​en la clase CCanvas: Rellenando canales con transparencia Indicadores basados ​​en la clase CCanvas: Rellenando canales con transparencia
En este artículo, analizaremos métodos utilizados para crear indicadores personalizados que se dibujan con la ayuda de la clase CCanvas de la Biblioteca estándar, y también consideraremos las propiedades de los gráficos para transformar coordenadas. Prestaremos especial atención a los indicadores que rellenan de transparencia el área entre las dos líneas.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 03):  Haciendo ajustes (I) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 03): Haciendo ajustes (I)
Pongamos las cosas en su sitio, porque este comienzo no ha sido de los mejores. Si no lo hacemos ahora, pronto tendremos problemas.