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

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 05): Vistas previas

MetaTrader 5Ejemplos | 7 julio 2023, 09:54
280 0
Daniel Jose
Daniel Jose

Introducción

En un artículo anterior, más precisamente en el artículo titulado "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 02): Primeros experimentos (II)", hemos logrado desarrollar una forma de ejecutar la repetición de mercado de manera bastante realista y aceptable. En este método, cada barra de 1 minuto se crea dentro de ese mismo minuto. Todavía necesitamos hacer algunos pequeños ajustes, pero eso se tratará más adelante. Este paso es extremadamente importante para que realmente podamos considerar la creación de un sistema capaz de simular la repetición de mercado.

A pesar de haber logrado realizar esta etapa, aparentemente, nuestra repetición de mercado no es muy adecuada para realizar estudios y para la práctica de un sistema operativo específico. Esto se debe a que no es posible realizar un pre-análisis de los datos anteriores (ya sean de minutos, horas o días), lo que nos impide obtener resultados viables.

Cuando menciono pre-análisis, me refiero a la posibilidad de tener barras anteriores al momento en que el sistema crea las barras desde un punto específico. Sin estas barras anteriores, no es posible obtener información válida de ningún indicador.

Piensa en lo siguiente: tú tienes un archivo con todos los ticks de trading ejecutados en un día determinado. Sin embargo, utilizando solo el contenido de ese archivo, no serás capaz de obtener información realmente útil de ningún indicador. Incluso si utilizas una media móvil de 3 períodos, por ejemplo, que es exactamente la utilizada en el sistema JOE DI NAPOLI, ninguna señal se generará hasta que al menos 3 barras hayan sido creadas. Solo en ese momento, la media móvil se mostrará en el gráfico. En términos de practicidad, hasta el momento actual, este sistema es completamente inútil e inviable.

Piensa en la situación en la que deseas realizar estudios en un intervalo de tiempo de 5 minutos. Necesitarías esperar 15 minutos para que la media móvil de 3 períodos se muestre en el gráfico. Y todavía pasarían unos minutos más antes de que realmente apareciera alguna señal útil. Es decir, el sistema necesita ser actualizado, y el objetivo de este artículo es tratar cómo hacer esa actualización.


Entendiendo algunos detalles

La parte de la implementación es algo bastante simple y relativamente rápido de hacer. Sin embargo, antes de escribir cualquier línea de código, necesitamos considerar otro punto.

El punto a considerar, por extraño que pueda parecer, es el siguiente: ¿qué período gráfico voy a usar en la repetición y cuál es el período mínimo necesario para que cada indicador genere alguna información útil? Estas preguntas son muy importantes y necesitan ser respondidas adecuadamente. No existe un modelo estándar, todo dependerá de cómo tú pretendes realizar tus estudios en la repetición de mercado.

Pero si estas cuestiones se responden adecuadamente, no te limitarás solo a la repetición. También podrás crear un simulador de mercado, que es algo extremadamente interesante y mucho más atractivo. Sin embargo, él sigue los mismos principios de funcionamiento que la repetición. La única diferencia real entre la repetición y el simulador es la fuente de los datos (TICKS o BARRAS) utilizados para generar el estudio.

En el caso de la repetición, los datos son reales, mientras que en el caso del simulador, los datos son sintetizados de alguna manera, ya sea basándose en un patrón matemático, o por pura casualidad. Y aquí es donde surge algo bastante curioso en el caso del simulador. Muchas instituciones e incluso operadores experimentados utilizan métodos matemáticos para generar un tipo específico de estudio, llamado prueba de estrés, en el que simulan movimientos de mercado con el fin de prever posibles movimientos extremos en diferentes escenarios. Sin embargo, esto se abordará en el futuro, ya que implica aspectos que aún no se han explicado.

Pero no te preocupes, en el futuro, cuando este sistema de repetición esté en una etapa más avanzada, explicaré cómo puedes "crear" tales datos. Por el momento, vamos a centrarnos en cómo utilizar datos reales de mercado, es decir, una repetición.

Para determinar el número de barras anteriores necesarias en nuestra repetición, necesitamos realizar un cálculo bastante simple:


donde N es el número de barras necesarias, P es el período más grande que vamos a utilizar o necesitar para que una media o indicador comience a proporcionar información útil, y T es la cantidad de minutos existente en el período gráfico. Para que quede más claro, veamos los ejemplos a continuación:

Ejemplo 01:

Supongamos que vas a utilizar una media móvil de 200 períodos en el gráfico y un tiempo gráfico de 5 minutos. ¿Cuántas barras, como mínimo, serán necesarias en el gráfico?

n = ( 200 * 5 ) + 5

Esto es igual a 1005, es decir, será necesario tener al menos 1005 barras de 1 minuto antes de iniciar la repetición, para que se pueda trazar la media de 200. Esto es válido para el uso de tiempo gráfico igual o inferior a 5 minutos. De esta manera, la media aparecerá justo al comienzo de la repetición.

Ejemplo 02:

Quieres usar una media móvil de 21 períodos, una de 50 períodos, el RSI configurado con 14 períodos y un VWAP intradiario para tus estudios.  Realizarás el estudio en tiempos gráficos de 5 y 15 minutos. Sin embargo, ocasionalmente, comprobarás el gráfico de 30 minutos para confirmar tus operaciones de entrada o salida. ¿Cuántas barras, como mínimo, necesitarás tener antes de iniciar la repetición?

Para quienes tienen poca experiencia, esta situación puede parecer complicada, pero en realidad es algo simple. Sin embargo, es necesario pensar un poco y prestar atención a los detalles. Vamos a entender cómo hacer el cálculo para obtener el número mínimo de barras. Primero, debes verificar cuál es el período más grande a ser usado en las medias o indicadores. En este caso, el valor es 50, ya que el VWAP es un indicador atemporal y no necesitamos preocuparnos por él. Este es el primer punto. Ahora, vamos a verificar el tiempo gráfico. En este caso, debemos usar el tiempo de 30, ya que es el mayor de los tres que se usarán. Aunque el gráfico de 30 minutos se utilice solo una vez, debemos considerarlo como el valor mínimo. Entonces el cálculo sería así:

n = ( 50 * 30 ) + 30

Esto es igual a 1530, es decir, será necesario tener al menos 1530 barras de 1 minuto antes de iniciar la repetición de hecho.

A pesar de parecer mucho, este número está lejos de lo que sería necesario en casos más extremos. Sin embargo, la idea es que siempre tengas una cantidad de datos mucho mayor de la que muchos imaginan que es necesaria para realizar estudios o análisis.

Ahora surge otra cuestión. Normalmente, en B3 (Bolsa Brasileña), la sesión de trading (período de negociación) ocurre en varias etapas y con ventanas de tiempo diferentes para algunas clases de activos. Normalmente, los mercados de futuros comienzan a negociar a las 9:00 y terminan a las 18:00. Sin embargo, en algunos momentos del año, este período se extiende hasta las 18:30. Mientras que el mercado de acciones funciona de las 10:00 a las 18:00. Hay otros mercados y activos que tienen horarios de negociación específicos. Por lo tanto, es necesario verificar estos parámetros antes de continuar. Intenta seguir la idea. Debido al hecho de que B3 tiene una ventana de negociación, muchas veces tendrás que descargar datos de varios días para obtener la cantidad mínima de barras calculada.

Nota importante: Aunque estemos tratando la cantidad como número de barras, no debes, de ninguna manera, olvidar que las barras son generadas por negociaciones de ticks, es decir, la cantidad de datos necesarios será mucho mayor. Sin embargo, para evitar confusiones, prefiero usar el número de barras para facilitar toda la explicación.

Entonces, necesitamos otro cálculo. Él determinará la cantidad mínima de días a capturar. Esto se obtiene usando la siguiente fórmula:


donde N es el número de días, F es la hora entera del cierre de la ventana de negociación, K es la hora entera del inicio de la ventana de negociación y M es el número de minutos fraccionarios dentro de esa ventana. Para ilustrar, veamos un ejemplo de cómo hacer el cálculo:

El futuro del dólar inició sus negociaciones a las 9:00 y terminó a las 18:30. ¿Cuántas barras de 1 minuto tuvimos ese día?

n = (( 18 - 9 ) * 60 ) + 30

Esto es igual a 570 barras de 1 minuto.

Normalmente, este valor ronda las 540 barras de 1 minuto, ya que el mercado suele cerrar la ventana de negociación a las 18:00. Sin embargo, este número no es exacto, ya que las negociaciones pueden sufrir interrupciones durante el día debido a subastas que pueden ocurrir intradía, lo que complica un poco la situación. De todas formas, tendrás, como máximo, 570 barras de 1 minuto por día de negociación, ya que los futuros son los que tienen la ventana de negociación más grande.

Bueno, sabiendo esto, divides la cantidad de barras necesarias por la cantidad de barras diarias que el activo utilizará en la repetición. Esta cantidad diaria es un promedio. Así, tendrás la cantidad mínima de días que deben adquirirse para que los datos de las medias e indicadores se representen correctamente en el gráfico.


Implementación

La intención aquí es motivarte para aprender a construir tus propias soluciones. Quizás pueda parecer repetitivo, pero pequeños cambios en un sistema pueden causar grandes diferencias. Ya que tendremos que usar un modelo de codificación que intente mantener el código lo más rápido posible, al mismo tiempo que no pierda estabilidad. El primer cambio se ve a continuación:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user00 = "WIN$N_M1_202108030900_202108051754";  //Arquivo de Barras ( Prev. )
input string    user01 = "WINQ21_202108060900_202108061759";    //Arquivo com ticks ( Replay )
//+------------------------------------------------------------------+
C_Replay        Replay;

Se ha agregado una línea que será responsable de indicar qué archivo contiene los datos previos a ser utilizados. En el futuro, cambiaremos esto, pero por ahora se mantendrá de esta manera. No quiero que te confundas y no entiendas cómo se está desarrollando el código. Así, cambiará poco a poco, permitiéndote aprender de él.

El hecho de que podamos indicar solo un único archivo implica que, si necesitas utilizar varios días, tendrás que hacer que MetaTrader 5 genere un archivo con varios días incrustados. Dicho así, parece complicado, pero es mucho más sencillo y fácil de hacer de lo que puede parecer.


Observa en la figura de arriba que las fechas son diferentes. Con esto, capturaremos todas las barras necesarias para que podamos hacer nuestros estudios. Ten en cuenta que tenemos un total de 1605 barras de 1 minuto capturadas, lo que nos permite un amplio rango de trabajo. En los ejemplos de cálculo, la cantidad era mucho menor.

¡¿Pero por qué tenemos que usar barras de 1 minuto? ¡¿Por qué no usar barras de 5 o 15 minutos?!

El motivo es que, al usar barras de 1 minuto, tendrás la posibilidad de utilizar cualquier tiempo gráfico. La propia plataforma MetaTrader 5 creará las barras de tiempo gráfico diferentes para nosotros, lo que es muy práctico, ya que no tendremos que preocuparnos por hacer ningún tipo de ajuste. Ahora, si utilizas una base de datos con barras de tiempo de 15 minutos, por ejemplo, no podrás utilizar otros tiempos gráficos de manera simple. Esto se debe a que no podrás contar con la ayuda que la plataforma MetaTrader 5 nos proporcionará al usar barras de 1 minuto. Por este motivo, siempre deberás, preferentemente, utilizar un tiempo de barras de 1 minuto.

A pesar de todo, hasta se puede hacer uso de una base de datos de tiempo gráfico diferente, pero eso no se tratará ahora. Veremos eso cuando abordemos las cuestiones que involucran la simulación.

Incluso si no quieres capturar varios días, es posible capturar los días por separado y luego unir los archivos. Sin embargo, no te desesperes todavía. Existe otra solución que evita tener que hacer las cosas de esta manera, pero no la abordaré en este artículo. Los próximos pasos del código se pueden ver a continuación:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        Replay.LoadPrevBars(user00);
        if (!Replay.LoadTicksReplay(user01)) return;
        Print("Aguardando permissão para iniciar replay ...");

Declaramos algunas variables que serán utilizadas por el servicio. Inmediatamente después, hacemos la llamada para definir el símbolo que se usará en la repetición. Es importante que esta llamada sea lo primero que se haga de hecho, ya que todo lo demás se hará directamente dentro del símbolo creado.

Ahora cargamos las barras previas. Observa que hay una secuencia lógica aquí, pero esta función de carga de barras previas tiene algunos detalles interesantes, que no son totalmente explorados en este momento. Por ahora, continuaremos viendo cómo se inicializa el sistema.

Después de cargar los valores previos, si existen, cargamos los ticks negociados. Este es el punto en el que realmente nos preparamos para la repetición. Si todo está correcto, se imprimirá un mensaje en el terminal indicando que el sistema está en modo de espera.

        id = Replay.ViewReplay();
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
        t1 = GetTickCount64();

Ahora el sistema permanecerá en este modo de espera, esperando la carga del TEMPLATE de repetición y la apertura y visualización del gráfico del activo de repetición. Cuando esto suceda, el bucle de espera se terminará,

ya que en este caso se habrá creado una variable global de terminal, permitiendo la comunicación entre el servicio de repetición y el indicador de control. Este indicador fue creado y se puede estudiar en los artículos "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 03): Haciendo ajustes (I)"y "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 04): Haciendo ajustes (II)".

En cuanto se cargue el indicador de control, se imprimirá un mensaje en la consola. Ahora el usuario podrá dar play al sistema. Por último, capturamos el número de ticks de la máquina para generar una forma de verificar el tiempo transcurrido.

Una vez hecho esto, entramos en el bucle que ya fue explicado con detalles en los artículos anteriores. Sin embargo, esta fue solo la primera parte de la implementación. Todavía tenemos otras cosas que ver que merecen una atención especial.


Una nueva clase C_Replay

Aunque pueda parecer extraño, fue necesario hacer algunos cambios internos en la clase responsable de mantener la repetición. Voy a detallar los nuevos cambios con calma, para que podáis comprender qué son y por qué ocurrieron. Empezamos por las variables, que ahora son otras.

int      m_ReplayCount;
datetime m_dtPrevLoading;
long     m_IdReplay;
struct st00
{
        MqlTick Info[];
        int     nTicks;
}m_Ticks;

Este nuevo conjunto de variables es suficiente para nuestro trabajo. Si miráis el código anterior, notaréis que ahora el conjunto es diferente. Por eso, otras cosas también han cambiado. Ahora tenemos una nueva rutina para inicializar el símbolo de repetición.

void InitSymbolReplay(void)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        SymbolSelect(def_SymbolReplay, true);
}

Empezamos eliminando, si está presente, el símbolo de repetición de la ventana de observación del mercado. Esto solo ocurrirá si el símbolo no está en un gráfico abierto.

A continuación, borramos el mismo para finalmente crearlo como un activo personalizado. ¿Pero por qué todo este trabajo? Tranquilos, después entenderéis el motivo de esto. Después de que se haya creado el activo personalizado, lo colocamos en la ventana de observación del mercado para que podamos manipularlo y colocarlo en un gráfico. Esta rutina de inicialización tendrá que sufrir cambios en el futuro, pero por ahora es suficiente para lo que queremos y necesitamos hacer. Recordad que necesitamos hacer funcionar las cosas primero antes de modificarlas.

La siguiente rutina que sufrió cambios es la de carga de los ticks negociados.

#define macroRemoveSec(A) (A - (A % 60))
                bool LoadTicksReplay(const string szFileNameCSV)
                        {
                                int     file,
                                        old;
                                string  szInfo;
                                MqlTick tick;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        Print("Carregando ticks de replay. Aguarde...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray);
                                        old = m_Ticks.nTicks = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file) + " " + FileReadString(file);
                                                tick.time = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                tick.bid = StringToDouble(FileReadString(file));
                                                tick.ask = StringToDouble(FileReadString(file));
                                                tick.last = StringToDouble(FileReadString(file));
                                                tick.volume_real = StringToDouble(FileReadString(file));
                                                tick.flags = (uchar)StringToInteger(FileReadString(file));
                                                if ((m_Ticks.Info[old].last == tick.last) && (m_Ticks.Info[old].time == tick.time) && (m_Ticks.Info[old].time_msc == tick.time_msc))
                                                        m_Ticks.Info[old].volume_real += tick.volume_real;
                                                else
                                                {
                                                        m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                        m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0);
                                                        old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                                                }
                                        }
                                }else
                                {
                                        Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                                        return false;
                                }
                                return true;
                        };
#undef macroRemoveSec

En primer lugar, intentamos abrir el archivo que debería contener los ticks de las operaciones ejecutadas. Presta atención a este punto: en este momento, aún no estamos haciendo pruebas de verificación. Por lo tanto, ten cuidado al indicar el archivo correcto.

El archivo debe estar ubicado en la carpeta indicada dentro del área del MQL5. Pero, no debes indicar la extensión del archivo, ya que por defecto es la extensión CSV. Si se encuentra el archivo, comenzaremos la carga. Si no se encuentra, el servicio de repetición no podrá ejecutarse y se mostrará un mensaje en el cuadro de mensajes informando del motivo del fallo del sistema.

Vamos ver qué sucede en caso de que se encuentre el archivo de ticks negociados. En este caso, lo primero que haremos será saltar el contenido de la primera línea del archivo, que por ahora no nos es útil. Después de eso, entraremos en un bucle para leer la información. Lo primero que hacemos es capturar la fecha y la hora en que se negoció el tick y luego almacenar la información en una posición temporal. A continuación, capturamos cada uno de los valores en el orden correcto. Observa que no necesitamos indicar nada, el propio formato del archivo CSV, junto con la forma en que el MQL5 realiza la lectura, ya es suficiente para que los datos sean leídos correctamente.

Ahora hacemos una prueba para verificar la siguiente condición: si el precio anterior de la última operación es igual al precio de la operación actual y los horarios también son iguales, el volumen actual se sumará al volumen anterior. Pero, para que esto ocurra, ambos tests deben ser verdaderos.

Observa que no me preocupo por la cuestión del flag, es decir, no importa si fue una compra o una venta. Si las demás informaciones son iguales, esto no afectará la repetición, al menos por ahora, ya que el precio simplemente no se movió. Ahora, si la condición de pruebas falla, tendremos la indicación de que estamos leyendo un tick diferente. En este caso, vamos a añadirlo a nuestra matriz de ticks. Un detalle: si el tick es un ajuste en el BID o ASK, no habrá volumen en él. Por lo tanto, no añadiremos una nueva posición y esta posición será sobrescrita cuando se lea un nuevo tick. En caso de tener algún volumen, la posición será incrementada.

Y así seguimos en este bucle hasta que se alcance el final del archivo o el límite de ticks. Pero ten cuidado, porque todavía no estamos haciendo ninguna prueba, y estas pruebas ayudan a evitar ciertas fallas. Pero, como el sistema aún es bastante simple y la cantidad de datos a utilizar aún es pequeña, podemos dejar pasar algunas pequeñas fallas. Sin embargo, con el tiempo, esta rutina tendrá un aumento en la cantidad de pruebas para evitar problemas que, por ahora, no nos cuestan mucho.

La siguiente rutina de la lista tiene su código expuesto justo debajo para que podamos analizarlo con calma.

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) FileReadString(file);
                Print("Carregando barras previas para Replay. Aguarde ....");
                while (!FileIsEnding(file))
                {
                        Rate[0].time = StringToTime(FileReadString(file) + " " + FileReadString(file));
                        Rate[0].open = StringToDouble(FileReadString(file));
                        Rate[0].high = StringToDouble(FileReadString(file));
                        Rate[0].low = StringToDouble(FileReadString(file));
                        Rate[0].close = StringToDouble(FileReadString(file));
                        Rate[0].tick_volume = StringToInteger(FileReadString(file));
                        Rate[0].real_volume = StringToInteger(FileReadString(file));
                        Rate[0].spread = (int) StringToInteger(FileReadString(file));
                        iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust);
                        dt = (dt == 0 ? Rate[0].time : dt);
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
                m_dtPrevLoading = Rate[0].time + iAdjust;
                FileClose(file);
        }else
        {
                Print("Falha no acesso ao arquivo de dados das barras previas.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return true;
}

Lo que hace este código de arriba es cargar todas las barras previas necesarias o pertenecientes a un archivo determinado para la repetición. Sin embargo, a diferencia de la rutina anterior, donde era necesario almacenar la información para su uso posterior, aquí lo hacemos de manera diferente. Vamos leyendo la información y añadiéndola inmediatamente al activo. Sigue la explicación para entender cómo ocurre esto.

Primero, intentamos abrir el archivo de barras. Observa que tenemos el mismo comando utilizado en la versión de ticks. Sin embargo, la ubicación es diferente y el contenido del archivo también es diferente. Pero la forma de acceder a la información será bastante similar. Si logramos abrir el archivo con éxito, comenzaremos saltando la primera línea, ya que no nos interesa en este momento.

Esta vez, entramos en un bucle que sólo terminará en la última línea del archivo. No hay límite para el tamaño o cantidad de datos presentes en este archivo. Todos los datos serán leídos. Los datos leídos, siguen el modelado OHCL, por lo que los colocamos en una estructura temporal. Una vez hecho esto, tenemos un punto en el que debemos prestar mucha atención. ¿Cómo podemos saber dónde comienza la repetición y dónde terminan las barras previas?

Fuera de esta rutina, sería un poco complicado saberlo. Pero aquí tenemos la indicación exacta de dónde terminan las barras previas y dónde comienza la repetición. Es exactamente en este punto donde almacenaremos esta posición temporal para uso posterior. Ahora, observa que con cada línea de barras leídas, actualizaremos inmediatamente las barras contenidas en el activo de repetición. De esta manera, no tendremos trabajo extra posteriormente. Así, en el momento en que se hayan leído las barras previas, ya podremos añadir nuestros indicadores.

Vamos a la siguiente rutina, que es simple, pero muy importante.

long ViewReplay(void)
{
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);                            
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

Lo que hace esta rutina es abrir un gráfico del activo de repetición en el tiempo gráfico estándar de 1 minuto. Puedes utilizar otro tiempo gráfico estándar para evitar tener que cambiarlo constantemente.

Una vez hecho esto, cargamos una plantilla que debe contener obligatoriamente el indicador de repetición. Si el indicador no está presente en la plantilla, no podrás reproducir el servicio. A pesar de esto, es posible usar otra plantilla, pero en ese caso necesitarás agregar manualmente el indicador de repetición al gráfico del activo que se está utilizando para ejecutar la repetición. Si no te importa hacer esto, está bien, siéntete libre. Sin embargo, al usar la plantilla con el indicador, tendrás acceso inmediato al sistema, ya que haremos una actualización forzada del gráfico y devolveremos el índice del gráfico para verificar si está disponible o no.

Así como iniciamos el sistema, también necesitamos cerrarlo, y para eso contamos con una rutina propia.

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

El gran detalle aquí es que el orden en que se hacen las cosas es muy importante. Si cambias el orden, las cosas pueden no suceder como se espera. Por lo tanto, primero cerramos el gráfico del activo, lo eliminamos de la ventana de observación del mercado, lo excluimos de la lista de símbolos personalizados y, finalmente, eliminamos la variable global del terminal. Con esto, el servicio de repetición se cerrará.

La siguiente rutina también ha sufrido algunos cambios.

inline int Event_OnTime(void)
{
        bool    bNew;
        int     mili, iPos;
        u_Interprocess Info;
        static MqlRates Rate[1];
        static datetime _dt = 0;
                                
        if (m_ReplayCount >= m_Ticks.nTicks) return -1;
        if (bNew = (_dt != m_Ticks.Info[m_ReplayCount].time))
        {
                _dt = m_Ticks.Info[m_ReplayCount].time;
                Rate[0].real_volume = 0;
                Rate[0].tick_volume = 0;
        }
        mili = (int) m_Ticks.Info[m_ReplayCount].time_msc;
        do
        {
                while (mili == m_Ticks.Info[m_ReplayCount].time_msc)
                {
                        Rate[0].close = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].open = (bNew ? Rate[0].close : Rate[0].open);
                        Rate[0].high = (bNew || (Rate[0].close > Rate[0].high) ? Rate[0].close : Rate[0].high);
                        Rate[0].low = (bNew || (Rate[0].close < Rate[0].low) ? Rate[0].close : Rate[0].low);
                        Rate[0].real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                        bNew = false;
                        m_ReplayCount++;
                }
                mili++;
        }while (mili == m_Ticks.Info[m_ReplayCount].time_msc);
        Rate[0].time = m_Ticks.Info[m_ReplayCount].time;
        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
        iPos = (int)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
        GlobalVariableGet(def_GlobalVariableReplay, Info.Value);
        if (Info.s_Infos.iPosShift != iPos)
        {
                Info.s_Infos.iPosShift = iPos;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
        }
        return (int)(m_Ticks.Info[m_ReplayCount].time_msc < mili ? m_Ticks.Info[m_ReplayCount].time_msc + (1000 - mili) : m_Ticks.Info[m_ReplayCount].time_msc - mili);
}

Lo que hace esta rutina de arriba es crear una barra de 1 minuto y añadirla al activo de repetición. Sin embargo, aquí tenemos algo que puede afectar un poco tu comprensión de por qué la repetición está mostrando algunas cosas y otras no.

Observa que el volumen de los ticks siempre será cero. ¿Y por qué es esto? Para entenderlo, consulta el documento "". Si lo has leído y no has entendido el documento o no has entendido por qué el volumen de los ticks es siempre cero, estás ignorando un hecho. Por lo tanto, vamos a intentar entender por qué este valor siempre está en cero.

Cuando leemos los datos de los ticks negociados, estamos leyendo los ticks reales, es decir, el volumen que se muestra allí es real. Por ejemplo, si una orden tiene un volumen igual a 10, esto indica que hubo una negociación con un volumen real de 10 unidades mínimas requeridas, y no que se usaron 10 ticks. No es posible generar un volumen de 1 tick negociado. Sin embargo, hay una situación en la que de hecho tenemos un volumen de ticks, que es cuando se produce una orden de apertura y cierre, resultando en un volumen de 2 ticks. En la práctica, sin embargo, tenemos un volumen mínimo de ticks igual a 3. Como estamos utilizando ticks de negociación reales, necesitamos hacer un ajuste en el valor a presentar. Por ahora, aún no he añadido este cálculo al sistema de repetición, por lo que solo se mostrará el valor real negociado.

Es importante tener cuidado con esto, porque, aunque parezca lo mismo, el volumen real indica cuántas negociaciones realmente ocurrieron, mientras que el volumen de ticks indica cuántos movimientos ocurrieron.

Entiendo que, en este momento, esto parece muy confuso y difícil de comprender, pero en futuros artículos, cuando abordemos el mercado de forex y discutamos cómo hacer repetición y simulaciones en ese mercado, todo se aclarará y se explicará mejor. Por lo tanto, no te preocupes por intentar entender completamente este tema ahora. Con el tiempo, lo comprenderás mejor.

La última rutina que se tratará en este artículo se muestra a continuación:

int AdjustPositionReplay()
{
        u_Interprocess Info;
        MqlRates Rate[1];
        int iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks);
                                
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (iPos < m_ReplayCount)
        {
                CustomRatesDelete(def_SymbolReplay, m_dtPrevLoading, LONG_MAX);
                m_ReplayCount = 0;
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        };
        for (iPos = (iPos > 0 ? iPos - 1 : 0); m_ReplayCount < iPos; m_ReplayCount++) Event_OnTime();
        return Event_OnTime();
}

En esta rutina, solo hay un punto diferente de los artículos anteriores, y ese punto hará la corrección para que podamos comenzar a insertar los ticks negociados. De esta manera, todo lo que esté por encima de este punto se considera como repetición de ticks de negociaciones reales, y debajo de este punto, los valores son de las barras previas. Si solo proporcionáramos un valor igual a cero en este punto, toda la información grabada en el activo de repetición sería eliminada. Tendrías que volver a leer las barras de 1 minuto de los datos para obtener los valores de las barras previas. En resumen: sería un trabajo totalmente innecesario y mucho más complicado de hacer. Por lo tanto, todo lo que necesitamos hacer es indicar el punto en el que ocurre esta transición, y MetaTrader 5 eliminará las barras que fueron creadas por los ticks reales, facilitando mucho nuestra vida.


Conclusión

Todo el resto de la rutina ya se explicó en el artículo Desarrollo de un sistema de repetición — Simulación de mercado (Parte 04): Haciendo ajustes (II), así que no entraré en detalles de nuevo aquí.

En el video a continuación, puedes ver el sistema funcionando, donde se demostrará cómo es posible agregar varios indicadores al sistema de repetición.




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

Archivos adjuntos |
Market_Replay.zip (6102.2 KB)
Teoría de categorías en MQL5 (Parte 4): Intervalos, experimentos y composiciones Teoría de categorías en MQL5 (Parte 4): Intervalos, experimentos y composiciones
La teoría de categorías es una rama de las matemáticas diversa y en expansión, relativamente inexplorada aún en la comunidad MQL5. Esta serie de artículos tiene como objetivo describir algunos de sus conceptos para crear una biblioteca abierta y seguir utilizando esta maravillosa sección para crear estrategias comerciales.
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.
Algoritmos de optimización de la población: Algoritmo electromagnético (ElectroMagnetism-like algorithm, ЕМ) Algoritmos de optimización de la población: Algoritmo electromagnético (ElectroMagnetism-like algorithm, ЕМ)
El artículo describe los principios, métodos y posibilidades del uso del algoritmo electromagnético (EM) en diversos problemas de optimización. El algoritmo EM es una herramienta de optimización eficiente capaz de trabajar con grandes cantidades de datos y funciones multidimensionales.
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.