English Русский 中文 Deutsch 日本語 Português
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 10): Sólo datos reales para la repetición

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 10): Sólo datos reales para la repetición

MetaTrader 5Ejemplos | 21 agosto 2023, 08:24
208 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 09): Eventos personalizados, vimos cómo activar eventos personalizados. Al mismo tiempo, hemos producido un sistema muy interesante, en términos de cómo el indicador se comunica con el servicio. En este artículo, insistiré en la importancia de almacenar en archivos los ticks negociados. Si aún no has adoptado esta práctica, deberías planteártelo seriamente haciéndolo a diario. Registra todos los valores negociados del activo que quieras estudiar en profundidad.

No es productivo buscar una solución milagrosa para recuperar la información perdida. Una vez perdida, no hay forma de recuperarla, sea cual sea el método. Si tu objetivo es realmente estudiar y comprender en profundidad un activo específico, no lo dejes para más tarde. Empieza cuanto antes a guardar los ticks negociados en un lugar seguro, ya que su valor es incalculable. Con el tiempo, te darás cuenta de que los datos de las barras pueden no coincidir con los valores de los ticks negociados. Este desajuste puede complicar la comprensión, sobre todo para los menos familiarizados con el mercado financiero.

Ocurre que, ocasionalmente, ciertos acontecimientos influyen en los valores, provocando, por ejemplo, que las barras de 1 minuto no reflejen las operaciones reales. Algunos de estos eventos son: anuncios de ganancias, agrupación o fraccionamiento del activo, vencimiento del activo (en el caso de futuros) donde el valor se ajusta periódicamente, y emisión de bonos o suscripciones. Todos ellos son ejemplos de situaciones que pueden distorsionar el valor actual en las barras de 1 minuto, haciendo que no se correspondan con la realidad negociada en un momento o periodo determinado.

Esto se debe a que cuando suceden tales acontecimientos, el activo tiene su precio ajustado de una manera específica. Por mucho que lo intentemos, no podremos realizar un ajuste que iguale las barras de 1 minuto a los ticks negociados. Por eso los ticks tienen un valor incalculable.

NOTA: Algunos pueden argumentar que sumando la diferencia del ajuste, los valores serán equivalentes. Sin embargo, esa no es realmente la cuestión. El dilema es que si el mercado percibe el precio en un rango determinado, reacciona de una manera. Si lo percibe en otro, la reacción es diferente.

Por lo tanto, tenemos que hacer una adaptación en nuestro sistema de repetición/simulador, utilizando exclusivamente los archivos de ticks negociados. Aunque esto se ha hecho desde el principio de esta serie de artículos, todavía no estamos utilizando realmente los datos contenidos en los ticks negociados, excepto para generar la repetición. Sin embargo, esto va a cambiar. Ahora también permitiremos que estos archivos, y en consecuencia los datos contenidos en ellos, se utilicen para generar las barras previas. Esto sustituirá al método actual, en el que sólo utilizamos archivos con barras de previsualización de 1 minuto.


Desarrollo del código

Gran parte del código ya está implementado. Por lo tanto, no tendremos mucho trabajo en este aspecto. Sin embargo, tenemos que pensar en el archivo de configuración utilizado en la repetición/simulación. Desde que empezamos a utilizar este archivo, es evidente que es de suma importancia para nosotros. El reto, aunque no es exactamente un problema, es que este fichero tiene dos estructuras: una para listar los ticks negociados, que se utilizarán para crear la repetición/simulación, y otra para indicar las barras que se utilizarán como vista previa del movimiento, que no se incluirán en la simulación, sino que sólo se añadirán al activo.

Esto nos permite separar y ajustar adecuadamente la información, pero ahora nos enfrentamos a un pequeño problema: si se incluye un archivo de ticks dentro de la estructura de barras, el sistema generará una alerta de error. Queremos que esto siga siendo así para no correr el riesgo de crear un estudio de repetición/simulación en el que las cosas se nos vayan de las manos por un error tipográfico al crear el fichero de configuración. Lo mismo ocurre si se intenta añadir un fichero de barras de 1 minuto como si fuera un fichero de ticks negociados: el sistema simplemente lo interpretará como un error.

Entonces, ¿cómo podemos resolver parte de este dilema y utilizar archivos de ticks como barras previas sin que el sistema lo identifique como un error? Aquí es donde entra la solución. Sugeriré una de varias soluciones posibles y factibles de desarrollar. El primer paso será añadir una nueva definición al fichero de cabecera C_Replay.mqh.

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_STR_TicksToBars     "[TICKS->BARS]"

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

Esta definición servirá para identificar cuándo uno o más archivos de ticks deben ser tratados como archivos de barras. Esto facilitará los pasos siguientes. Sin embargo, hay un detalle adicional: no nos limitaremos a añadir esta definición. También permitiremos al usuario modificarla, al igual que las demás definiciones, pero de forma sutil. Esto hará las cosas potencialmente más claras y comprensibles para el usuario. Sin embargo, para el sistema no habrá grandes cambios, ya que seguirá interpretando todo correctamente.

Así, si al usuario se le ocurre introducir en el fichero de configuración la siguiente definición:

[ TICKS -> BARS ]

Eso seguirá siendo y debe entenderse como una definición válida. Con esto hemos ampliado un poco las opciones, para que sea más fácil de entender para el usuario. La razón es que algunos prefieren no agrupar los datos, sino separarlos de forma lógica, lo cual es perfectamente aceptable, y podemos permitirlo. Para hacer esta flexibilización, que permite al usuario "cambiar" ligeramente las definiciones previstas, tenemos que añadir algunos detalles al código del servicio. Esto hará que el código sea más comprensible y, al mismo tiempo, nos permitirá ampliar futuras funcionalidades de la forma más sencilla posible. Para ello, crearemos un enumerador que nos ayude. Sin embargo, hay un detalle en esta creación, como verás a continuación:

class C_Replay
{
        private :
                enum eTranscriptionDefine {Transcription_FAILED, Transcription_INFO, Transcription_DEFINE};
                int      m_ReplayCount;
                datetime m_dtPrevLoading;

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

Este enumerador es privado para la clase C_Replay, es decir, no es accesible fuera de la clase. Aunque aseguramos esto colocándolo en el bloque de declaraciones privadas, también ganamos la capacidad de ampliar fácilmente la complejidad del fichero de configuración. Sin embargo, este detalle será cubierto en los próximos artículos de la serie, ya que es una explicación demasiado extensa para cubrir en este artículo.

Después de este paso, podemos centrarnos en el siguiente punto a implementar. Este punto es en realidad una rutina que nos permite identificar la clasificación de los datos contenidos en el archivo de configuración. Veamos cómo funciona este proceso. Para ello, utilizaremos el siguiente código:

inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
{
        string szInfo;
        
        szInfo = In;
        Out = "";
        StringToUpper(szInfo);
        StringTrimLeft(szInfo);
        StringTrimRight(szInfo);
        if (StringSubstr(szInfo, 0, 1) != "[")
        {
                Out = szInfo;
                return Transcription_INFO;
        }
        for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                if (StringGetCharacter(szInfo, c0) > ' ')
                        StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                
        return Transcription_DEFINE;
}

Lo que pretendemos aquí es centralizar todo el proceso de pretraducción de los datos que se obtendrán del fichero de configuración. Independientemente de cómo vaya a ser el trabajo posterior, la función anterior realizará toda la comprobación previa para asegurar que los datos recuperados siguen un patrón concreto. Para ello, lo primero que hay que hacer es convertir todos los caracteres a mayúsculas. Después, eliminamos cualquier elemento extra que sea innecesario para los siguientes pasos. Una vez hecho esto, comprobamos el primer carácter de la secuencia y, si es distinto de "[", tendremos la indicación de que la secuencia representa alguna información y no una definición.

En este caso concreto y en este momento, simplemente devolveremos el resultado del proceso realizado anteriormente. Si no es así, empezaremos a buscar los datos de la definición. Aunque tengan un formato diferente, el contenido puede ser adecuado y correcto. Al leer, ignoramos cualquier carácter cuyo valor sea inferior al espacio. Así, aunque escribas: [ B A R S ], el sistema interpretará [BARS]. Con esto, la entrada puede tener ligeras variaciones en la forma de escribir, pero mientras el contenido sea el esperado, obtendremos el resultado adecuado.

Ahora tenemos un nuevo sistema de lectura y configuración basado en el contenido del fichero de configuración:

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. Aguarde....");
        ArrayResize(m_Ticks.Rate, def_BarsDiary);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        while ((!FileIsEnding(file)) && (!_StopFlag)) switch (GetDefinition(FileReadString(file), szInfo))
        {
		case Transcription_DEFINE:
			if (szInfo == def_STR_FilesBar) isBars = true; else
                        if (szInfo == def_STR_FilesTicks) isBars = false;
                        break;
		case Transcription_INFO:
			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;
			}
			break;
	}
        FileClose(file);

        return (!_StopFlag);
}

Sin embargo, no te emociones demasiado. El único cambio aquí fue la estructura que estaba allí antes. La forma de hacer las cosas sigue siendo la misma. Todavía no podemos utilizar archivos que contengan ticks negociados como si fueran barras de vista previa de 1 minuto. Tenemos que ajustar el código anterior.

Si te fijas bien, verás que hemos adquirido la capacidad de hacer algo que antes era imposible. Piensa: los datos se envían a un procedimiento que centraliza todo el preprocesamiento de los datos leídos del fichero de configuración. Los datos utilizados en el código anterior ya están limpios. Por lo tanto, ¿podemos incluir comentarios en el fichero de configuración? Y la respuesta es: . Ya podemos hacerlo. Sólo tenemos que definir el formato de un comentario, ya sea de una o varias líneas. Pero empecemos por uno de una sola línea. Vea que el procedimiento para hacerlo es simple y directo:

inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
{
        string szInfo;
                                
        szInfo = In;
        Out = "";
        StringToUpper(szInfo);
        StringTrimLeft(szInfo);
        StringTrimRight(szInfo);
        if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
        if (StringSubstr(szInfo, 0, 1) != "[")
        {
                Out = szInfo;
                return Transcription_INFO;
        }
        for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                if (StringGetCharacter(szInfo, c0) > ' ')
                        StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                                
        return Transcription_DEFINE;
}

Cuando el carácter indicado se encuentra al principio de una línea, esa línea será tratada como un comentario y todo su contenido será ignorado. Por lo tanto, ahora puedes insertar comentarios en el archivo de configuración, interesante, ¿no? Con la simple adición de una línea en nuestro código, ahora soportamos comentarios. Sin embargo, volvamos al problema principal. Nuestro código todavía no trata los archivos de ticks negociados como barras de 1 minuto. Para ello, necesitamos hacer algunas modificaciones. Antes de realizar dichos cambios, nos aseguraremos de que el sistema sigue funcionando como antes, pero con algunas funcionalidades nuevas. Así, obtenemos el siguiente código:

bool SetSymbolReplay(const string szFileConfig)
        {
#define macroERROR(MSG) { FileClose(file); if (MSG != "") MessageBox(MSG, "Market Replay", MB_OK); return false; }
                int     file,
                        iLine;
                string  szInfo;
                char    iStage;
                                
                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. Aguarde....");
                ArrayResize(m_Ticks.Rate, def_BarsDiary);
                m_Ticks.nRate = -1;
                m_Ticks.Rate[0].time = 0;
                iStage = 0;
                iLine = 1;
                while ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        switch (GetDefinition(FileReadString(file), szInfo))
                        {
                                case Transcription_DEFINE:
                                        if (szInfo == def_STR_FilesBar) iStage = 1; else
                                        if (szInfo == def_STR_FilesTicks) iStage = 2; else
                                        if (szInfo == def_STR_TicksToBars) iStage = 3; else
                                                macroERROR(StringFormat("%s não é algo reconhecido dentro do sistema\nna linha %d.", szInfo, iLine));
                                        break;
                                case Transcription_INFO:
                                        if (szInfo != "") switch (iStage)
                                        {
                                                case 0:
                                                        macroERROR(StringFormat("Comando não reconhecido na linha %d\ndo arquivo de configuração.", iLine));
                                                        break;
                                                case 1:
                                                        if (!LoadPrevBars(szInfo)) macroERROR(StringFormat("Este arquivo esta declarado na linha %d", iLine));
                                                        break;
                                                case 2:
                                                        if (!LoadTicksReplay(szInfo)) macroERROR(StringFormat("Este arquivo esta declarado na linha %d", iLine));
                                                        break;
                                                case 3:
                                                        break;
                                        }
                                        break;
                        };
                        iLine++;
                }
                FileClose(file);
                return (!_StopFlag);
#undef macroERROR
        }

Al llamar a este procedimiento, la primera acción es inicializar ciertos parámetros para nuestro uso. Nótese que estamos contando las líneas, lo que es fundamental para que el sistema informe dónde se produjo el error. Debido a esto, tenemos una macro que proporciona un mensaje genérico en caso de errores, utilizado en diversas situaciones. Ahora, nuestro sistema funciona por pasos. Esto nos obliga a definir explícitamente lo que se procesará en las líneas siguientes. Omitir este paso se considerará un error. En este contexto, el primer error siempre será cero, ya que indicamos en la inicialización del procedimiento que nos encontramos en la etapa cero del análisis. 

Aunque esta estructuración parece compleja, permite ampliar de forma ágil y eficiente cualquier aspecto deseado. Las adiciones rara vez afectarán al código anterior. Sin embargo, la adopción de este enfoque ya nos permite utilizar el archivo de configuración con un formato interno como el que se presenta a continuación:

#Primeiro colocamos as barras previas imagino que 3 arquivos seja o suficiente ....
[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

#Tenho um arquivo de tickets mas vou usa-lo como barras previas ... com isto teremos 4 arquivos de barras
[ Ticks -> Bars]
WINQ21_202108050900_202108051759

#Agora vamos usar o arquivo de tickets negociados para executar o replay ...
[Ticks]
WINQ21_202108060900_202108061759

#Fim do arquivo de configuração...

Es decir, ahora se puede aclarar lo que está sucediendo, lo que simplifica la configuración y el uso de un formato más eficaz. Ten en cuenta que aún no hemos implementado realmente lo que queremos. Lo que estoy presentando es cómo las cosas pueden hacerse más interesantes con pequeños cambios en el código. Y estos cambios no son tan laboriosos como se podría pensar.

Pero sin más preámbulos. Vamos a implementar los cambios necesarios para utilizar los archivos de tick negociados como barras previas. Y antes de que empieces a imaginarte un código complejo, te informo de que el código necesario ya está listo en el servicio de repetición/simulador. Simplemente está oculto en medio de la complejidad. Lo que tenemos que hacer es extraer ese código y sacarlo a la luz. Este trabajo debe hacerse con mucho cuidado, ya que cualquier error puede poner en peligro todo el sistema ya establecido. Para que lo entiendas, fíjate en el siguiente código, mencionado en el artículo anterior:

bool LoadTicksReplay(const string szFileNameCSV)
{
        int     file,
                old;
        string  szInfo = "";
        MqlTick tick;
        MqlRates rate;
                        
        if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                ArrayResize(m_Ticks.Rate, def_BarsDiary, def_BarsDiary);
                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;
                                if (tick.volume_real > 0.0)
                                {
                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                        rate.spread = m_Ticks.nTicks;
                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        m_Ticks.nTicks++;
                                }
                                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);
};

Aunque este código se dedica a leer y almacenar los ticks negociados para su posterior uso como repetición/simulación, hay un punto crucial en él. En un momento dado, vamos a crear algo equivalente a una barra de 1 minuto, utilizando únicamente datos de ticks negociados.

Ahora reflexiona: ¿No es eso exactamente lo que queremos hacer? Leer un archivo de ticks negociados y crear una barra de 1 minuto y luego almacenarla en el activo utilizado para la repetición/simulación. Pero no como un tick negociado, sino como un dato que será interpretado como una barra anterior. Así, si en lugar de simplemente almacenar estos ticks, realizamos pequeños cambios en el código mencionado, podremos hacer que la barra creada durante la lectura de los ticks negociados se inserte en el activo para ser analizada en la repetición/simulación como una barra anterior.

Sin embargo, hay un detalle a tener en cuenta en esta tarea. Sin embargo, esto se tratará durante la explicación de la implementación, ya que es algo que sólo entenderás cuando veas el código realmente montado y funcionando. Pero primero, veamos el código responsable de convertir los ticks negociados en barras previas, que se encuentra justo debajo:

bool SetSymbolReplay(const string szFileConfig)
        {
#define macroERROR(MSG) { FileClose(file); MessageBox((MSG != "" ? MSG : StringFormat("Ocorreu um erro na linha %d", iLine)), "Market Replay", MB_OK); return false; }
                int     file,
                        iLine;
                string  szInfo;
                char    iStage;
                                
                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. Aguarde....");
                ArrayResize(m_Ticks.Rate, def_BarsDiary);
                m_Ticks.nRate = -1;
                m_Ticks.Rate[0].time = 0;
                iStage = 0;
                iLine = 1;
                while ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        switch (GetDefinition(FileReadString(file), szInfo))
                        {
                                case Transcription_DEFINE:
                                        if (szInfo == def_STR_FilesBar) iStage = 1; else
                                        if (szInfo == def_STR_FilesTicks) iStage = 2; else
                                        if (szInfo == def_STR_TicksToBars) iStage = 3; else
                                                macroERROR(StringFormat("%s não é algo reconhecido dentro do sistema\nna linha %d.", szInfo, iLine));
                                        break;
                                case Transcription_INFO:
                                        if (szInfo != "") switch (iStage)
                                        {
                                                case 0:
                                                        macroERROR(StringFormat("Comando não reconhecido na linha %d\ndo arquivo de configuração.", iLine));
                                                        break;
                                                case 1:
                                                        if (!LoadPrevBars(szInfo)) macroERROR("");
                                                        break;
                                                case 2:
                                                        if (!LoadTicksReplay(szInfo)) macroERROR("");
                                                        break;
                                                case 3:
                                                        if (!LoadTicksReplay(szInfo, false)) macroERROR("");
                                                        break;
                                        }
                                        break;
                        };
                        iLine++;
                }
                FileClose(file);
                        return (!_StopFlag);
#undef macroERROR
        }

Aunque hemos hecho una pequeña modificación en el sistema de aviso de errores para estandarizar los mensajes, ese no es el objetivo aquí. El verdadero interés de este código es la llamada que generará las barras de 1 minuto a partir de los ticks negociados. Vea que la llamada es idéntica a la que usamos antes, pero ahora con un parámetro extra. Este simple detalle del parámetro adicional marca la diferencia, evitando que tengamos que reescribir toda la función, como se puede ver en el siguiente código.

En la función original ya está presente toda la lógica necesaria para que el servicio de repetición/simulador procese los datos de un fichero de ticks negociados y los convierta en barras de 1 minuto. Lo que realmente tenemos que hacer es adaptar estas barras para que sean barras previas, sin poner en peligro el funcionamiento general. Porque sin las modificaciones adecuadas, se pueden generar errores al utilizar el servicio de repetición/simulador. Para que entiendas realmente cómo hacerlo y cuál es mi propuesta, ya que hay otros métodos posibles, vamos a analizar los cambios realizados en el código para la lectura de los ticks negociados. De esta forma, el sistema podrá utilizar estos ticks como barras previas.

bool LoadTicksReplay(const string szFileNameCSV, const bool ToReplay = true)
{
        int     file,
                old,
                MemNRates,
                MemNTicks;
        string  szInfo = "";
        MqlTick tick;
        MqlRates rate,
                RatesLocal[];
                                
        MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
        MemNTicks = m_Ticks.nTicks;
        if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                ArrayResize(m_Ticks.Rate, def_BarsDiary, def_BarsDiary);
                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;
                                if (tick.volume_real > 0.0)
                                {
                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                        rate.spread = (ToReplay ? m_Ticks.nTicks : 0);
                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        m_Ticks.nTicks++;
                                }
                                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...");
                        FileClose(file);
                        return false;
                }
                FileClose(file);
        }else
        {
                Print("Aquivo de ticks ", szFileNameCSV,".csv não encontrado...");
                return false;
        }
        if ((!ToReplay) && (!_StopFlag))
        {
                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
                m_dtPrevLoading = m_Ticks.Rate[m_Ticks.nRate].time;
                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
                m_Ticks.nTicks = MemNTicks;
                ArrayFree(RatesLocal);
        }
        return (!_StopFlag);
};

Lo primero que hay que notar es la adición de algunas variables adicionales. Estas variables locales almacenarán temporalmente información crucial para, si es necesario, restaurar el estado inicial del sistema más adelante. Es esencial darse cuenta de que todo el código seguirá de forma idéntica al original. Podríamos haber añadido las barras de 1 minuto a medida que iban apareciendo, pero yo opté por un enfoque diferente. Naturalmente, tuve que hacer una modificación en un punto concreto, donde valoramos si los datos se considerarán o no como barras previas. Si se utilizan de este modo, es fundamental que la presentación sea coherente.

Me aseguro de que la lectura se realice sin interferir en el procedimiento original. Los ticks negociados se leen y se convierten en barras de 1 minuto, ahí no hay cambios. Sin embargo, una vez que el archivo está completamente leído y todo es como se esperaba, entramos en una nueva fase. Aquí es donde tiene lugar la verdadera transformación. Si el fichero de ticks ha sido designado para ser utilizado como sistema de barras previas y el usuario no ha solicitado la finalización del servicio de repetición/simulador durante esta lectura, hemos encontrado una condición verdadera. A continuación, tomamos medidas específicas para garantizar la correcta utilización de los datos y devolver el sistema a su estado inicial. De este modo, evitamos problemas o situaciones atípicas durante la fase de reproducción.

El primer paso consiste en asignar un espacio de memoria para almacenar temporalmente las barras de 1 minuto. Una vez hecho esto, transferimos las barras construidas durante la lectura del fichero a este espacio temporal. Esta acción es crucial para el siguiente paso, que consiste en insertar las barras en el activo de repetición/simulación, garantizando su éxito. Sin este movimiento previo, sería complejo posicionar correctamente las barras de 1 minuto en el activo para que sean interpretadas como barras previas. Cabe mencionar que si optáramos por un método directo de añadir las barras durante la creación, toda esta lógica no sería necesaria. Sin embargo, con el método elegido, en el que primero leemos el fichero y luego grabamos las barras como previas, necesitamos estas maniobras para garantizar un funcionamiento correcto.

Al asignar y transferir los datos de esta forma, el proceso se simplifica, ya que no es necesario crear un bucle para la transferencia. Una vez finalizada la transferencia, ajustamos el valor de posición límite de las barras previas y restauramos los valores almacenados al inicio del procedimiento. De esta forma, al final, el sistema funcionará como si nada hubiera cambiado. Para él, parecerá como si la lectura de las barras previas se hubiera realizado directamente desde un fichero de barras de 1 minuto y no desde un fichero de ticks negociados. Las barras previas se mostrarán como era de esperar y por fin liberamos el espacio de memoria temporal.

Con un esfuerzo mínimo, superamos un reto importante. Sin embargo, la solución propuesta no es la única factible, pero quizá sí la que menos cambios requiere en el código original.


Eliminación de todos los gráficos de repetición

Hay un detalle en el sistema al momento de cerrarlo. Usualmente, esto ocurre cuando cerramos el gráfico que tiene el indicador de control. En efecto, el problema no es grave, es más un pequeño inconveniente. Muchas personas prefieren tener más de un gráfico abierto del mismo activo simultáneamente. Está bien, es comprensible y, en algunos escenarios, puede ser útil hacerlo. Sin embargo, consideren lo siguiente: cuando el servicio de repetición/simulación intenta eliminar cualquier rastro del activo utilizado, simplemente no logra hacerlo. La razón es simple: hay otro gráfico abierto con el activo de repetición.

De esta manera, el sistema no logra eliminar esos rastros. Queda un activo en la ventana de observación del mercado que no tiene ningún sentido. Aunque es posible retirarlo manualmente, esa no es la idea, y no debería ser así. El servicio de repetición/simulación debería borrar cualquier evidencia de manera completamente automática. Para solucionar esto, necesitaremos hacer un cambio en el código. Para comprender realmente lo que se agregará, veamos el código original a continuación:

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

Noten el comportamiento en este código: cuando se llama a este procedimiento, solo se cerrará el gráfico que fue abierto por el servicio. Si no hay ningún otro gráfico abierto que esté haciendo referencia al activo de repetición, este podrá ser eliminado de la ventana de observación del mercado. Pero como se mencionó, el usuario podría haber abierto otros gráficos que usan precisamente el activo de repetición del mercado. En este escenario, cuando intentamos quitar el activo de la ventana de observación del mercado, no lo logramos. Para resolver este inconveniente, necesitamos cambiar la forma en que el servicio se cierra. Solo será necesario ajustar las líneas relevantes para una solución más compleja pero también más eficaz. El código necesario para esto se muestra a continuación:

void CloseReplay(void)
{                       
        ArrayFree(m_Ticks.Info);
        ArrayFree(m_Ticks.Rate);
        m_IdReplay = ChartFirst();
        do
        {
                if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                        ChartClose(m_IdReplay);
        }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
        for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
        GlobalVariableDel(def_GlobalVariableIdGraphics);
}

Este código puede parecer extraño para muchos de ustedes, pero su función es bastante simple. Primero, capturamos la ID del primer gráfico. Cabe señalar que no necesariamente es el primero que se abrió. A continuación, iniciamos un bucle. Dentro de este bucle, hacemos una verificación para determinar el activo que se está referenciando en el gráfico. Si es el activo de repetición, cerraremos el gráfico. Para determinar si el bucle terminará o no, solicitamos a la plataforma que nos informe sobre la ID del siguiente gráfico en la lista. Si no hay más gráficos en la lista, se devolverá un valor menor que cero y el bucle terminará. De lo contrario, el bucle se ejecutará nuevamente. Esto asegura que todas las ventanas cuyo activo sea el que estamos usando como repetición se cerrarán, independientemente de cuántas sean. Luego, hacemos dos intentos para eliminar el activo usado como repetición de la ventana de observación del mercado. La razón de realizar dos intentos es que, cuando solo tenemos la ventana abierta por el servicio de repetición/simulación, un intento es suficiente para retirar el activo. Sin embargo, cuando hay otras ventanas abiertas por el usuario, puede ser necesario intentarlo una segunda vez.

Por lo general, un intento es suficiente. Si no logramos eliminar el activo de la ventana de observación, no podremos retirar el símbolo personalizado. Pero, independientemente de esto, eliminaremos cualquier contenido presente en el activo personalizado, ya que no sirve fuera del servicio de repetición/simulación. No queremos que quede ninguna información allí. Aunque no logremos eliminar completamente el activo personalizado, no será un gran problema, ya que no contendrá nada en su interior. Sin embargo, el objetivo de este procedimiento es, efectivamente, eliminarlo por completo de la plataforma.


Conclusión

En el vídeo que se muestra a continuación se puede ver el resultado del trabajo presentado en este artículo. Aunque algunas cosas no se aprecian visualmente, viendo el vídeo te harás una idea clara del progreso del sistema de repetición/simulador a lo largo de estos artículos. Sólo tienes que ver los vídeos y comparar los cambios desde el principio hasta ahora.



En el próximo artículo, continuaremos con el desarrollo de este sistema, ya que aún quedan por implementar algunas características que son realmente necesarias.


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

Archivos adjuntos |
Market_Replay.zip (13061.7 KB)
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 11): Nacimiento del SIMULADOR (I) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 11): Nacimiento del SIMULADOR (I)
Para poder usar datos que forman barras, debemos abandonar la repetición y comenzar a desarrollar un simulador. Utilizaremos las barras de 1 minuto precisamente porque nos ofrecen un nivel de complejidad mínimo.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 09): Eventos personalizados Desarrollo de un sistema de repetición — Simulación de mercado (Parte 09): Eventos personalizados
Aquí veremos cómo accionar eventos personalizados y mejorar la cuestión de cómo el indicador informa del estado del servicio de repetición/simulación.
Trading bursátil con cuadrícula usando un asesor con órdenes stop pendientes en la Bolsa de Moscú (MOEX) Trading bursátil con cuadrícula usando un asesor con órdenes stop pendientes en la Bolsa de Moscú (MOEX)
Hoy utilizaremos un enfoque comercial de cuadrícula con órdenes stop pendientes en un asesor experto en el lenguaje de estrategias comerciales MQL5 para MetaTrader 5 en la Bolsa de Moscú (MOEX). Al comerciar en el mercado, una de las estrategias más simples consiste en colocar una cuadrícula de órdenes diseñada para "atrapar" el precio del mercado.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 08): Bloqueo del indicador Desarrollo de un sistema de repetición — Simulación de mercado (Parte 08): Bloqueo del indicador
En este artículo te mostraré cómo bloquear un indicador, simplemente utilizando el lenguaje MQL5, de una forma muy interesante y sorprendente.