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

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

MetaTrader 5Probador | 11 julio 2023, 09:31
683 0
Daniel Jose
Daniel Jose

Introducción

Nuestro sistema de repetición de mercado ha sido documentado en artículos a medida que se va creando, el motivo de esto es mostrar a todo el mundo como está naciendo realmente todo, hasta que tome una forma más definitiva. Por ello, es posible que el progreso sea lento y que se creen, eliminen, añadan y modifiquen muchas cosas. La razón es que el sistema de repetición del mercado se está creando al mismo tiempo que se publican los artículos. Es decir, se realizan algunas pruebas, antes de empezar a documentarlo, para que el sistema permanezca estable y funcional, mientras se modela y ajusta.

Dicho esto, vayamos realmente al grano, en el artículo anterior Desarrollo de un sistema de repetición — Simulación de mercado (Parte 05): Vista previa, hemos construido el sistema de carga de la barra de vista previa. Aunque ese sistema funciona adecuadamente, tenemos algunos problemas. Lo más urgente es el hecho de que hay que crear un fichero de barras previo, que a menudo contiene varios días y no permite intercalar los datos, para tener una base mucho más utilizable.

Cuando creas un archivo de barras previas que contiene, por ejemplo, datos de una semana, desde el lunes hasta el viernes, no podrás utilizar la misma base de datos para hacer una repetición el jueves, por ejemplo. Será necesario crear una nueva base de datos solo para cumplir con esta condición. Y eso es TERRIBLE si lo pensamos detenidamente.

Además de esta inconveniencia, tenemos otros problemas, como la falta total de pruebas adecuadas para garantizar que estemos utilizando una base de datos apropiada. De esta manera, puedes usar accidentalmente archivos de barras como si fueran datos de ticks de operaciones ejecutadas, o viceversa. Esto causa grandes trastornos en nuestro sistema, lo que impide que funcione correctamente. También habrá otros pequeños cambios que se harán a lo largo de este artículo, pero vayamos a lo que importa.

Recuerda que explicaré cada uno de los puntos para que entiendas lo que estará sucediendo, o al menos esa es mi intención.


Implementando las mejoras

El primer cambio que necesitamos hacer es agregar dos nuevas líneas al archivo de servicio. Se muestran a continuación:

#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence

¿Y por qué hacer esto? Por la simple razón de que el sistema está compuesto por módulos y tenemos que asegurarnos, de alguna manera, de que estos módulos estén presentes cuando se utilice el servicio de repetición. El módulo más importante de todos es precisamente este indicador, que es responsable de permitirnos tener cierto control sobre lo que se hará.

Debo confesar que esta no es una de las mejores formas de hacerlo. Tal vez en el futuro, aquellos que mantengan la plataforma MetaTrader 5, junto con el lenguaje MQL5, realicen algunas adiciones, como directivas de compilación, para que realmente podamos forzar o mejor dicho, garantizar que un archivo se compile o que realmente deba existir. Pero, en ausencia de algo mejor, lo hacemos así.

Establecemos una directiva que indica que el servicio depende de algo más. Luego, agregamos esa misma cosa como un recurso dentro del servicio. Un detalle: el hecho de convertir esa cosa en un recurso no necesariamente nos permitirá usarlo como un recurso. Este es un caso específico.

Estas dos simples líneas asegurarán que el indicador presente en la plantilla a ser utilizado por la repetición sea realmente compilado cuando se compile el servicio de repetición. Esto evita que tú lo olvides hacerlo y, cuando la plantilla cargue el indicador y no lo encuentre, ocurrirá un fallo que solo se percibirá cuando nos demos cuenta de que el indicador usado para controlar el servicio no está en el gráfico.

Para evitar esto, ya nos aseguramos de que el indicador se compile junto con el servicio. Sin embargo, si utilizas una plantilla personal y luego añades manualmente el indicador controlador, puedes eliminar estas dos líneas encima del código del servicio. Su ausencia no hará ninguna diferencia en el código ni en el funcionamiento del servicio.

NOTA: Aunque forcemos la compilación, solo ocurrirá si el ejecutable del indicador no existe. Si el indicador ha sido modificado, compilar solo el servicio no hará que el indicador se compile.

Algunos pueden decir que esto se resolvería utilizando el modo proyecto de MetaEditor. Sin embargo, este modo proyecto no nos permite trabajar de la misma forma que en lenguajes como C/C++, donde usamos un archivo MAKE para controlar la compilación. Sería posible hacer esto usando un archivo BATCH, pero nos obligaría a salir de MetaEditor solo para compilar el código.

Continuando, ahora tenemos dos nuevas líneas:

input string            user00 = "Config.txt";  //Arquivo de configuração do Replay.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Tempo gráfico inicial.

Aquí tenemos algo realmente útil para nuestro servicio de repetición de mercado, esta cadena de texto es, en realidad, el nombre del archivo que contendrá las configuraciones del activo de repetición. Estas configuraciones incluyen, por el momento, qué archivos se utilizan para generar las barras previas y qué archivos se utilizarán para contener los ticks negociados.

Ahora podrás utilizar más de un archivo al mismo tiempo. Además, sé que a muchos les gusta utilizar un marco temporal específico al operar en el mercado y, al mismo tiempo, les gusta usar la pantalla completa. Entonces, esta línea te ayuda a especificar cuál será el marco temporal a utilizar desde el principio. Es algo muy sencillo, pero nos brinda mucha más comodidad, ya que podemos guardar estas configuraciones para su uso posterior. Podemos añadir más cosas aquí, pero por ahora, está bien.

Ahora tenemos algunas cosas más que deben entenderse,  las cuales se pueden ver en el código a continuación:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id = 0;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        if (!Replay.SetSymbolReplay(user00))
        {
                Finish();
                return;
        }
        Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
        id = Replay.ViewReplay(user01);
        while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(750);
        if ((_StopFlag) || (ChartSymbol(id) == ""))
        {
                Finish();
                return;
        }
        Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)) && (!_StopFlag))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = true;
                }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();
                        }
                }
        }
        Finish();
}

Originalmente, lo que hacíamos era simplemente esperar a que las cosas estuvieran funcionando. Pero a partir de ahora, vamos a asegurarnos de que realmente estén funcionando, y esto se logra probando las cosas. Así que ahora vamos a probar si los archivos que se utilizarán para hacer la repetición son realmente adecuados para ello. Probamos el retorno de la función que lee los datos contenidos en los archivos y, en caso de que haya un fallo, tendremos un mensaje de lo sucedido en la caja de herramientas de MetaTrader 5 y el servicio de repetición simplemente se cerrará, ya que no tenemos datos adecuados para su uso.

Si los datos se han cargado correctamente, un mensaje en la caja de herramientas lo indicará y podremos continuar. Luego, abrimos el gráfico del activo de repetición y esperamos permiso para continuar con los siguientes pasos. Sin embargo, puede suceder que durante esta espera el usuario cierre el servicio o cierre el gráfico del activo de repetición. Si eso ocurre, debemos finalizar el servicio de repetición. Si todo ocurre correctamente, entraremos en el bucle de la repetición, pero aun así, nos aseguraremos de que el usuario no cierre el gráfico ni finalice el servicio, ya que en caso de que eso ocurra, la repetición también deberá cerrarse.

Nótese que ahora no estamos simplemente imaginando que las cosas funcionarán. Ahora estamos asegurándonos de que realmente funcionen. Puede parecer extraño que este tipo de prueba no se estuviera realizando en versiones anteriores del sistema. Pero allí había otras cuestiones, por lo que cada vez que la repetición se cerraba o sus actividades se finalizaban por alguna razón, quedaba algo atrás. Pero ahora no, nos aseguraremos de que las cosas funcionen correctamente y no queden elementos sobrantes.

En última instancia, tenemos el siguiente código aún dentro del archivo de servicio:

void Finish(void)
{
        Replay.CloseReplay();
        Print("Serviço de replay finalizado...");
}

No es algo complicado de entender. Aquí simplemente estamos finalizando la repetición e informándole al usuario a través de la caja de herramientas. De esta manera, podemos pasar al archivo que implementa la clase C_Replay, donde también realizamos más pruebas para garantizar el correcto funcionamiento de las cosas.

Si lo observas detenidamente, no notarás grandes cambios en la clase C_Replay. Sin embargo, se está construyendo de manera que mantenga el servicio de repetición lo más estable y confiable posible. Por lo tanto, los cambios se realizarán gradualmente para no destruir todo el trabajo realizado hasta ahora.

Entonces, lo primero que llama la atención son las líneas mostradas a continuación:

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_Header_Bar          "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>"
#define def_Header_Ticks        "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>"

Aunque parezcan de poca importancia, estas 4 líneas son bastante interesantes, ya que realizan exactamente las pruebas necesarias. Estas dos definiciones aquí se utilizan en el archivo de configuración que se verá en breve. Esta contiene exactamente los datos que encontrarás en la primera línea de un archivo que contiene las barras a utilizar en este momento como barras previas. Esta definición contiene exactamente el contenido de la primera línea del archivo que contiene los ticks negociados.

Pero espera un momento, estas definiciones no son exactamente iguales a las que se encuentran en el encabezado de los archivos, faltan las tabulaciones. SÍ, de hecho, faltan las tabulaciones presentes en los archivos originales. Sin embargo, aquí hay un pequeño detalle relacionado con la forma en que se leen los datos.

Pero antes de entrar en ese detalle, veamos cómo es un archivo de configuración del servicio de repetición en la etapa actual de desarrollo. Un ejemplo de este archivo se muestra a continuación:

[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

[Ticks]
WINQ21_202108050900_202108051759
WINQ21_202108060900_202108061759

La línea definida como [Bars], que puede ser escrita así, ya que no utilizaré el caso sensible en el sistema, indica que todas las líneas siguientes se utilizarán como barras previas. Así que tenemos 3 archivos distintos que se cargarán en el orden indicado. Presta atención a esto, porque si los colocas fuera de orden, obtendrás una repetición completamente diferente al esperado. Todas las barras presentes en estos archivos se agregarán una a una al activo que se utilizará como repetición. No importa la cantidad de archivos o barras, todos los archivos se agregarán como barras previas hasta que algo indique cambiar este comportamiento.

En el caso de la línea [Ticks], esto le indicará al servicio de repetición que todas las líneas siguientes serán o deberán contener los ticks negociados, y estos se utilizarán para construir la repetición que deseamos estudiar. Del mismo modo que ocurre con las barras, nuevamente se aplica la misma advertencia aquí también, debes tener cuidado de colocar los archivos en el orden correcto. De lo contrario, la repetición será diferente a la esperada, la lectura siempre será desde el principio hasta el final del archivo. De esta manera, puedes combinar barras con ticks.

Sin embargo, existe una pequeña limitación en este momento. Tal vez no sea exactamente una limitación, ya que no tiene sentido agregar ticks negociados, hacer una repetición de ellos y luego ver aparecer más barras previas que se utilizarán en otro llamado de repetición, pero en una posición diferente. Pero el hecho de colocar los ticks antes de las barras previas en el archivo de configuración no hará ninguna diferencia para el sistema de repetición. Independientemente del orden, para el sistema de repetición, las barras previas siempre vendrán primero y solo después los ticks de negocios.

Importante: En el ejemplo anterior, no tuve en cuenta un hecho que es posible de hacer. Si deseas organizar mejor las cosas, puedes utilizar un árbol de directorios para separar y organizar de manera más adecuada. Esto se puede hacer sin realizar ninguna modificación adicional en el código de la clase. Todo lo que necesitarás hacer es tener cuidado de seguir una cierta lógica en las estructuras presentes en el archivo de la clase. Para que quede más claro, veamos un ejemplo de uso de un árbol de directorios para separar las cosas en términos de activos, meses o años.


Para comprender lo que se ha mencionado anteriormente, observa las imágenes a continuación:

                   

Observa que tenemos como RAÍZ el directorio MARKET REPLAY, que es la base que debemos usar una vez dentro de ese directorio. Podemos organizar las cosas separándolas en activos, años y meses, donde cada mes tendrá, dentro de él, los archivos correspondientes a lo que ocurrió en ese mes específico. De la forma en que se está creando el sistema, puedes utilizar una estructuración como se muestra arriba sin realizar ningún cambio en el código. Acceder a los datos específicos es simplemente una cuestión de informar eso en el archivo de configuración que estés utilizando.

Recuerda que este archivo de configuración puede tener cualquier nombre. Solo su contenido está estructurado. El nombre es libre para que elijas el más adecuado.

Muy bien. Entonces, en el momento en que vayas a llamar a un archivo, supongamos el archivo de ticks en el mini dólar del año 2020, correspondiente al mes de junio en el día 16, utilizarás la siguiente línea en el archivo de configuración:

[Ticks]
Mini_Dolar_Futuro\2020\06-Junho\WDO_16062020

Esto le indicará al sistema que lea exactamente el archivo en cuestión. Por supuesto, este es solo un ejemplo de cómo puedes organizar las cosas.

Pero, para entender por qué sucede esto y se vuelve posible, debemos ver la rutina que se encarga de leer este archivo de configuración. Así que vamos a ello.

bool SetSymbolReplay(const string szFileConfig)
{
        int     file;
        string  szInfo;
        bool    isBars = true;
                        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                MessageBox("Falha na abertura do\narquivo de configuração.", "Market Replay", MB_OK);
                return false;
        }
        Print("Carregando dados para replay.\nAguarde....");
        while ((!FileIsEnding(file)) && (!_StopFlag))
        {
                szInfo = FileReadString(file);
                StringToUpper(szInfo);
                if (szInfo == def_STR_FilesBar) isBars = true; else
                if (szInfo == def_STR_FilesTicks) isBars = false; else
                if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo)))
                {
			if (!_StopFlag)
	                        MessageBox(StringFormat("O arquivo %s de %s\nnão pode ser carregado.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK));
                        FileClose(file);
                        return false;
                }
        }
        FileClose(file);
        return (!_StopFlag);
}

Comenzamos intentando leer el archivo de configuración, que debe estar en una ubicación muy específica. Esta ubicación no se puede modificar, al menos después de que el sistema haya sido compilado. Si el archivo no se puede abrir, se mostrará un mensaje de error y la función se cerrará. Si el archivo se puede abrir, comenzamos a leerlo. Sin embargo, debes tener en cuenta que estaremos verificando constantemente si el usuario ha pedido al MetaTrader 5 que finalice el servicio de repetición.

Si esto ocurre, es decir, si el usuario finaliza el servicio, la función se cerrará como si hubiera fallado. Realizamos la lectura línea por línea y convertimos todos los caracteres leídos a sus versiones mayúsculas correspondientes. Esto facilita el análisis de las cosas. Luego, analizamos y llamamos a la rutina adecuada para leer los datos del archivo indicado en el script de configuración. Si la lectura de alguno de estos archivos falla por algún motivo, se mostrará un mensaje de error al usuario y la función terminará con un error. Una vez que se haya leído todo el archivo de configuración, la función simplemente finalizará. Y si no se ha cerrado por solicitud del usuario para finalizar el servicio, obtendremos un retorno indicando que todo está bien.

Ahora que hemos visto cómo se lee el archivo de configuración, echemos un vistazo a las rutinas responsables de la lectura de los datos y comprendamos por qué ahora informan que el archivo solicitado no cumple con lo esperado. Es decir, si intentas usar un archivo que contiene barras en lugar de un archivo que debería contener ticks negociados, o viceversa, el sistema reportará un error. Veamos cómo sucede esto.

Comencemos con la rutina más sencilla, que es responsable de la lectura y carga de las barras previas. El código responsable de esto se puede ver a continuación:

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
        string  szInfo = "";
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Bar)
                {
                        Print("Arquivo ", szFileNameCSV, ".csv não é um arquivo de barras prévias.");
                        return false;
                }
                Print("Carregando barras previas para Replay. Aguarde ....");
                while ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        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 (!_StopFlag);
}

A primera vista, este código no parece muy diferente al código contenido en el artículo anterior, Desarrollo de un sistema de repetición (Parte 05). Pero sí, contiene diferencias, y estas son bastante significativas en términos generales y estructurales.

La primera diferencia es el hecho de que ahora estamos capturando los datos del encabezado del archivo que se está leyendo. Este encabezado se compara con un valor definido y esperado por la rutina de lectura de las barras previas. Si este encabezado es diferente al esperado, se generará un error y la rutina se cerrará. Pero si es el esperado, entraremos en un bucle.

Anteriormente, la salida de este bucle solo estaba controlada por el final del archivo que se estaba leyendo. Si el usuario finalizaba el servicio por cualquier motivo, no se cerraría. Ahora esto ha sido corregido y, si el usuario finaliza el sistema de repetición durante la lectura de uno de los archivos que corresponden a las barras previas que se están cargando, se cerrará el bucle y se generará un error, indicando que el sistema ha fallado. Pero esto es solo una formalidad para que las cosas se cierren de manera un poco más suave y no abrupta.

Todo el resto de la rutina sigue ejecutándose de la misma manera, ya que no ha sido necesario realizar cambios en la forma de leer los datos.

Ahora veamos la rutina de lectura de los ticks negociados, que ha sido modificada y se ha vuelto aún más interesante que la rutina de lectura de las barras previas. Su código se muestra a continuación:

#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)
                        {
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                old = m_Ticks.nTicks;
                                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                                if (szInfo != def_Header_Ticks)
                                {
                                        Print("Arquivo ", szFileNameCSV, ".csv não é um arquivo de tick negociados.");
                                        return false;
                                }
                                Print("Carregando ticks de replay. Aguarde...");
                                while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), 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);
                                        }
                                }
                                if ((!FileIsEnding(file))&& (!_StopFlag))
                                {
                                        Print("Excesso de dados no arquivo de tick.\nNão é possivel continuar...");
                                        return false;
                                }
                        }else
                        {
                                Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                                return false;
                        }
                        return (!_StopFlag);
                };
#undef macroRemoveSec

Hasta el artículo anterior, esta rutina de lectura de ticks negociados tenía una limitación que se expresaba en la siguiente línea de definición:

#define def_MaxSizeArray        134217727 // 128 Mbytes de posições

Esta línea aún se mantiene, pero la limitación se ha eliminado, al menos en parte. Ya que hay interés en construir un sistema de repetición capaz de trabajar con más de una base de datos de ticks negociados. De esta manera, podrías agregar 2 o más días al sistema y realizar un estudio de repetición un poco más amplio. Además, existen casos muy, pero muy específicos, en los que puedes tener un archivo con más de 128 Mbytes de posiciones para trabajar. Tales casos son raros, pero pueden ocurrir. A partir de ahora, puedes usar un valor más bajo para esta definición anterior, para optimizar un poco más el uso de la memoria.

Pero espera un momento. Dije: ¿REDUCIR? SÍ. Y si observas la nueva definición, verás el siguiente código:

#define def_MaxSizeArray        16777216 // 16 Mbytes de posições

Puedes estar pensando: Estás loco, esto perjudicará al sistema... En realidad, no. Si observas la rutina de lectura de los ticks negociados, verás dos líneas bastante interesantes que no estaban allí anteriormente. Estas líneas son responsables de garantizar que podamos leer y almacenar un máximo de 2 elevado a 32 posiciones. Esto se garantiza mediante la prueba realizada al comienzo del bucle. 

Para asegurarnos de que las primeras posiciones no se pierdan, restamos 2 para que la prueba no falle por cualquier motivo. Podrías agregar un bucle externo adicional para ampliar esta capacidad de almacenamiento, pero personalmente, no veo razón para hacerlo. Si 2 gigas de posiciones no son suficientes, no sé qué sería. Pero vamos a entender con calma cómo reducir el valor de la definición nos asegura una mejor optimización, utilizando dos líneas para ello. Observemos con más detalle y en detalle el fragmento responsable de esto.

// ... Código anterior ....

if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
{
        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
        old = m_Ticks.nTicks;
        for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
        if (szInfo != def_Header_Ticks)
        {
                Print("Arquivo ", szFileNameCSV, ".csv não é um arquivo de tick negociados.");
                return false;
        }
        Print("Carregando ticks de replay. Aguarde...");
        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
        {
                ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                szInfo = FileReadString(file) + " " + FileReadString(file);

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

La primera vez que asignamos memoria, asignaremos todo el tamaño definido, más un valor de reserva. Este valor de reserva será nuestro salvavidas. Entonces, cuando entremos en el bucle de lectura, tendremos una secuencia de realocaciones, pero solo cuando sea necesario hacerlo.

Ahora, observa el hecho de que en esta segunda asignación, estamos utilizando el valor actual del contador de ticks ya leídos más 1. ¿Y por qué eso? Cuando estaba probando el sistema, noté que ejecutaba esta llamada con un valor igual a 0, lo que generaba un error en tiempo de ejecución. Cosas locas, podrías pensar, ya que la memoria ya se había asignado anteriormente con un valor mayor. Y ahí está la cuestión, la propia documentación de la función ArrayResize nos informa que vamos a REDEFINIR el tamaño del array.

Cuando usamos esta segunda llamada, la función REDEFINIRÁ el array con el valor cero. Esto se debe a que ese es el valor actual de la variable en la primera llamada de la rutina, y el motivo es que no se ha incrementado. No estoy aquí para explicar el motivo de esto, pero es necesario tener cuidado al trabajar con asignación dinámica en MQL5. Porque puede suceder que tu código parezca correcto, pero el sistema no lo interpretará de la forma en que imaginas.

Aquí hay otro pequeño detalle a tener en cuenta. ¿Por qué estoy usando INT_MAX y no UINT_MAX en la prueba? En realidad, lo ideal sería usar UINT_MAX, lo que nos daría 4 gigas de espacio asignado, pero observa que la función ArrayResize trabaja con un sistema INT, es decir, un entero con signo.

E incluso si deseas asignar 4 gigas, que sería posible al usar el tipo con 32 bits de longitud, siempre perderemos 1 bit en la longitud de los datos debido al signo. Por lo tanto, de hecho, estaremos utilizando 31 bits, lo que nos garantiza 2 gigas de espacio posible para asignar utilizando la función ArrayResize. De alguna manera, podríamos sortear esta limitación utilizando un esquema de intercambio, lo que nos garantizaría los 4 gigas de asignación, o incluso más, pero no veo motivo para utilizar este recurso. Dos gigas de datos ya son bastante.

Después de la explicación, volvamos al código. Aún tenemos algunas cosas más que ver sobre la rutina de lectura de los ticks negociados. Para probar si los datos del archivo son realmente ticks negociados, leemos y almacenamos los valores que se encuentran en el encabezado del archivo. Una vez hecho esto, finalmente podemos comprobar si el encabezado es el mismo que el sistema espera encontrar en un archivo de ticks negociados. De lo contrario, se mostrará un error y se cerrará el sistema.

De la misma manera que en la lectura de las barras, aquí también probamos si se ha solicitado la finalización del sistema por parte del usuario. Esto garantiza que tengamos una salida más suave y limpia. Ya que si el usuario cierra o finaliza el servicio de repetición, no queremos que se impriman algunos mensajes de error, generando confusión.

Además de todas estas pruebas realizadas aquí, tenemos algunas cosas más por hacer. En realidad, no sería necesario hacer estas cosas, pero no quiero dejar todo en manos de la plataforma. Quiero asegurarme de que algunas cosas se ejecuten realmente. Por este motivo, surge una nueva línea en nuestro código, que se puede ver a continuación:

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

Puede que pienses que ejecutar esta llamada no es del todo importante. Sin embargo, una de las buenas prácticas de programación es limpiar todo lo que se ha creado o devolver explícitamente toda la memoria que hemos asignado. Y eso es exactamente lo que se hace en este punto. Nos aseguramos de que la memoria asignada durante la carga de los ticks negociados se devuelva al sistema operativo. Normalmente, esto se hace cuando cerramos la plataforma o finalizamos el programa en el gráfico. Pero es bueno asegurarse de que se haga, incluso si la plataforma ya lo hace por nosotros. Tenemos que estar seguros de eso.

Si ocurre alguna falla y el recurso no se devuelve al sistema operativo, puede suceder que, cuando intentemos usar el recurso nuevamente, no esté disponible. Esto no ocurre debido a un fallo de la plataforma o el sistema operativo, sino a un olvido al programar alguna utilidad que se ejecutará.


Conclusión

En el siguiente video, podrás observar el funcionamiento del sistema en la etapa actual de desarrollo. Observa que las barras previas terminan el día 4 de agosto. El primer día de repetición comienza con el primer tick del día 5 de agosto. Sin embargo, puedes avanzar la repetición al día 6 de agosto y luego volver al inicio del día 5 de agosto. Esto no era posible en la versión anterior del sistema de repetición, pero ahora tenemos esta funcionalidad.

Si observas detenidamente, notarás un fallo en el sistema. Este fallo será corregido en el próximo artículo, donde mejoraremos aún más nuestra repetición de mercado, haciéndola más estable e intuitiva de usar.



En el archivo adjunto, tendrás acceso al código fuente y a los archivos utilizados en el video, para que puedas entender y practicar cómo armar el archivo de configuración. Es importante que comiences a estudiar esta etapa ahora, ya que el archivo de configuración cambiará con el tiempo, pero no de forma destructiva, sino para aumentar sus capacidades. Por lo tanto, entiéndelo desde ahora, desde el principio...


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

Archivos adjuntos |
Market_Replay.zip (13057.37 KB)
Teoría de Categorías en MQL5 (Parte 5): Ecualizadores Teoría de Categorías en MQL5 (Parte 5): Ecualizadores
La teoría de categorías es un apartado diverso y en expansión de las matemáticas, que solo recientemente ha comenzado a ser trabajado por la comunidad MQL5. Esta serie de artículos tiene por objetivo repasar algunos de sus conceptos para crear una biblioteca abierta y seguir usando este maravilloso apartado en la creación de estrategias comerciales.
Uso de modelos ONNX en MQL5 Uso de modelos ONNX en MQL5
ONNX (Open Neural Network Exchange) es un estándar abierto para representar modelos de redes neuronales. En este artículo, analizaremos el proceso de creación de un modelo CNN-LSTM para pronosticar series temporales financieras, y también el uso del modelo ONNX creado en un asesor experto MQL5.
Encontrando patrones de velas con la ayuda de MQL5 Encontrando patrones de velas con la ayuda de MQL5
En este artículo, hablaremos sobre cómo detectar automáticamente patrones de velas con la ayuda de MQL5.
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.