English Русский 中文 Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 13): Nacimiento del SIMULADOR (III)

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 13): Nacimiento del SIMULADOR (III)

MetaTrader 5Probador | 20 septiembre 2023, 09:23
258 0
Daniel Jose
Daniel Jose


Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 12): Nacimiento del SIMULADOR (II)", fue una preparación para este artículo aquí. Hoy, haremos algunos cambios en el sistema de simulación para lograr una mayor consistencia en los datos que se informan. Al mismo tiempo, realizaremos algunos cambios, bastante drásticos, para tener un sistema más eficiente en términos de procesamiento. Necesitaremos esto en las próximas etapas de la construcción de nuestro sistema de repetición/simulación. Pero la gran cuestión es que, para que el sistema sea realmente utilizable, ya sea como repetición o como simulador, necesitamos que tenga un comportamiento constante o, al menos, lo más constante y consistente posible. No podemos simplemente forzar al sistema a funcionar de una manera en algunos momentos y que en otros tenga un funcionamiento completamente diferente e impredecible.

En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 02): Primeros experimentos (II)", creamos un sistema que hasta ahora era adecuado para su uso, no es tan adecuado cuando la idea involucra simulaciones y generación de datos pseudoaleatorios. Y, para ser honesto, incluso si estás trabajando con una repetición (utilizando tickets reales), el sistema actual no es del todo adecuado. Esto es especialmente cierto si el activo o el día en cuestión tiene mucha volatilidad. En este tipo de escenario, el sistema de presentación y creación de barras de 1 minuto que se encuentra en el sistema actual es muy poco eficaz. Esto puede provocar problemas de sincronización en algunos momentos. En otras palabras, las barras que deberían construirse en 1 minuto a veces pueden tardar mucho más en crearse. Esto nos da la falsa impresión de que un movimiento de alta volatilidad es fácil de seguir u operar, lo cual no es cierto.

Resolver esto está lejos de ser una tarea sencilla. Esto se debe a que tendremos que cambiar la forma en que esto se construye realmente. Puedes estar pensando que esta tarea será sencilla, pero no lo será. Implica un tipo de modelado que la hace bastante complicada si no sabes lo que estás haciendo. Incluso yo, que estoy mostrando cómo hacerlo, me llevó todo este tiempo darme cuenta de que había algo mal en el sistema de construcción de las barras. Solo me di cuenta cuando comenzó la fase de simulación, donde las diferencias en el tiempo realmente se hicieron evidentes, ya que involucra algunos cálculos que veremos cómo realizar. Pero incluso ahora, no creas que podré resolver este problema. Se resolverá en otra fase de construcción. Pero eso es un tema para el futuro. Comencemos haciendo algunas correcciones y presentemos el nuevo sistema de construcción de barras de 1 minuto.


Un nuevo servicio de repetición del mercado

Para realmente construir las barras de 1 minuto de manera que podamos auditarlas si así lo deseamos, necesitaremos hacer algunos cambios en el servicio de repetición. Lo primero que debe modificarse es el archivo del servicio. Observa a continuación en su totalidad cómo será el nuevo archivo del servicio de repetición.

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"
#property description "Serviço de Replay-Simulador para plataforma MT5."
#property description "Este é dependente do indicador Market Replay."
#property description "Para mais detalhes sobre esta versão veja o artigo:"
#property description "https://www.mql5.com/pt/articles/11034"
#property link "https://www.mql5.com/pt/articles/11034"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //Arquivo de configuração do Replay.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Tempo gráfico inicial.
input bool              user02 = true;          //Visualizar a construção das barras.
input bool              user03 = true;          //Visualizar métricas de criação.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        Replay;
        
        Replay.InitSymbolReplay();
        if (Replay.SetSymbolReplay(user00))
        {
                Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
                if (Replay.ViewReplay(user01))
                {
                        Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
                        while (Replay.LoopEventOnTime(user02, user03));
                }
        }       
        Replay.CloseReplay();
        Print("Serviço de replay finalizado...");
}
//+------------------------------------------------------------------+

Notarás que es mucho más simple, al menos aparentemente. Toda la complejidad se ha trasladado al interior de la clase objeto, y esto tiene una razón muy fuerte: EL TIEMPO. Hay cuestiones sutiles aquí que explicaré a medida que avance el artículo. Pero de la forma en que estaba el antiguo archivo de servicio, algunas operaciones estaban consumiendo milisegundos preciosos del sistema. A pesar de intentar que la plataforma MetaTrader 5 se ejecutara de manera más eficiente, estos milisegundos consumidos, al final de cuentas, estaban degradando el rendimiento de manera que las barras de 1 minuto estaban tardando más tiempo en procesarse y construirse de hecho.

Sin embargo, si observas, verás que se ha agregado una nueva variable para el usuario. Esto te permite crear y realizar una auditoría del tiempo que se está gastando o que es necesario para crear las barras de 1 minuto. Nota que ahora tenemos solo una única llamada que bloqueará el bucle de creación de las barras. Esta llamada no regresará, excepto en dos situaciones muy específicas. Pero pronto entenderemos cuáles son estas situaciones. Este bucle, entonces, funcionará como un bucle infinito, pero en realidad, será regulado por el propio procedimiento que estará dentro de la clase objeto. Debido a esta simplificación, el archivo de servicio es en realidad solo eso. En cuanto a la clase objeto, como muchos de ustedes ya deben estar pensando, ha aumentado su complejidad. Como resultado, algunas funciones que antes eran públicas han dejado de serlo y ahora son privadas de la clase. Esto se llama ocultación de método. Por lo tanto, las únicas cosas verdaderamente públicas son las funciones que están presentes y que aparecen en el archivo del servicio, que se puede ver arriba.

De esta manera, comenzaremos a ver los cambios que han ocurrido y que permitirán una mayor simplificación del código presente en el archivo de servicio, como acabas de ver arriba. El primero de los cambios se muestra a continuación:

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

Básicamente, el código es igual que antes. Sin embargo, hemos agregado las pruebas que antes se realizaban en el código del archivo de servicio. De esta manera, el archivo de servicio ya no necesita saber cuál es el ID del gráfico que se está utilizando para mostrar el activo de repetición. Así que este punto esperará a que la variable global del terminal sea creada por el indicador. Pero si el usuario cierra el gráfico o detiene el servicio, este bucle finalizará. Sin embargo, si todo está bien y la variable está definida sin que el gráfico o el servicio hayan sido cerrados por el usuario, obtendremos un valor VERDADERO, y así el servicio continuará hacia la siguiente fase, que es precisamente el procedimiento que se muestra a continuación.

                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }

Esta rutina anterior es mucho más de lo que parece. Puedes pensar que entra y sale constantemente, pero en realidad solo regresa al código del archivo de servicio en dos casos. El primero es si el usuario pausa la creación de las barras de 1 minuto. Y el segundo es cuando el servicio es finalizado por el usuario, ya sea al cerrar el gráfico o porque no tenemos más ticks para usar. En resumen, cuando el servicio se ha cerrado por alguna razón. En cualquier otro caso, tendremos un valor VERDADERO si se ha agotado el suministro de ticks y un valor FALSO en cualquier otra hipótesis. Y el valor FALSO finalizará el sistema de repetición/simulación, como se puede observar al analizar el código del servicio.

Pero vamos a entender qué sucede el resto del tiempo en el que esta rutina simplemente queda atrapada dentro de los bucles internos que contiene. Sí, tienes razón, tenemos 2 bucles, cada uno responsable de algo muy específico. Vamos a enfocarnos en ambos para que comprendas lo que se está haciendo. El primer bucle que tenemos se destaca en el fragmento a continuación:

// ... declaração das variáveis ...

                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;

//... restante do código que será visto em seguida ...

Este fragmento anterior cuenta con un bucle que estará operativo en dos situaciones. La primera es que el servicio no haya sido finalizado mediante el cierre del gráfico, pero observa que también estamos probando una variable. Entonces, ¿qué estamos haciendo? Observa que dentro del bucle estaremos probando algunas condiciones para modificar el valor de la variable. Pero, ¿de dónde provienen estas pruebas? Estas pruebas se ejecutaban incluso en la versión anterior dentro del código del servicio. Sin embargo, existía un problema. Cada vez que se ejecutaban, estas pruebas consumían algunos milisegundos. Pero lo que más consumía ciclos de la máquina era precisamente comprobar si el gráfico estaba abierto o no.

El hecho de realizar esta prueba antes de que el sistema comience realmente a construir las barras de 1 minuto nos ahorra este consumo de ciclos de máquina. Sin embargo, necesitamos una forma de salir de este bucle. Entonces, si el usuario decide iniciar el servicio, tendremos una indicación de que el bucle debe finalizar. Para asegurarnos de que salga en dirección al sistema de construcción de barras, establecemos la variable de prueba en un valor positivo. Ahora, si por alguna razón el bucle termina sin que sea porque el usuario ha iniciado la construcción de las barras, devolveremos un valor FALSO. De esta manera, el servicio sabrá que la repetición/simulación debe finalizarse.

Sin embargo, si se cumple la condición de inicio, tendremos dos cosas que hacer en realidad. La primera es encontrar el punto de inicio de la repetición/simulación. Esta rutina responsable de esto se verá más adelante en este artículo. La segunda cosa que se debe hacer es restablecer el sistema de retraso. De esta manera, podremos ingresar al segundo bucle que se muestra a continuación.

// ... Código do laço anterior ...

                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               

// ... Restante da rotina ...

Este segundo bucle es responsable del funcionamiento correcto del sistema. Aquí es donde realmente construiremos las barras de 1 minuto. El sistema no saldrá de este bucle a menos que no haya más datos para utilizar o que el sistema se haya finalizado. Existe otra condición para salir del sistema, que es cuando el usuario pausa. En este caso, la rutina se detendrá y luego se llamará nuevamente, volviendo al primer bucle que se vio anteriormente. Ahora, veamos por qué este sistema es más eficiente que el sistema anterior en la versión anterior del servicio de repetición/simulación. Aquí realizamos una llamada a una función que se verá más adelante. Esta función creará las barras de 1 minuto. No te preocupes por su funcionamiento por ahora. Solo debes saber que la construcción se realiza en otro lugar.

Pero ahora viene la gran pregunta: Si observas con atención, verás que cada uno de los ticks, ya sea que se negocien de verdad o sean simulados, debe crearse en un momento determinado. Si aún no has visto esto, mira en la imagen a continuación el área resaltada:

Observa que tenemos la hora, los minutos y los segundos, que para nosotros no hacen mucha diferencia. Lo que realmente importa para nosotros aquí, en la repetición/simulación, es el número que viene después de los segundos, es decir, los milisegundos. Al observar estos números, notarás que pueden parecer mucho tiempo. Sin embargo, lo que realmente debemos entender no es el momento en milisegundos, sino la diferencia entre el tiempo del último tick y lo que vamos a mostrar a continuación. Al observar esto, notarás que en varios casos, la diferencia es muy pequeña, a veces menos de 2 milisegundos. El sistema anterior no era capaz de lidiar con esto; necesitamos un sistema más rápido. A pesar de nuestros intentos, no podía funcionar adecuadamente cuando los tiempos eran muy cortos, como se muestra en la imagen de arriba. Estos tiempos entre una llamada y otra son mucho menos de 10 milisegundos.

Pero con este nuevo método de construcción de las barras de 1 minuto, somos capaces de reducirlo a menos de 1 milisegundo, en computadoras modernas. Y dado que el procedimiento de construcción de las barras se ha vuelto bastante rápido, como verás más adelante, ya no necesitamos recurrir a OpenCL para obtener un rendimiento adecuado. Podemos lograrlo utilizando solo la CPU, sin necesidad de utilizar la GPU. Sin embargo, debes tener en cuenta que ya no haremos un retraso en cada tick. Acumularemos un poco las cosas para luego hacer una breve pausa. El valor acumulado y el valor de la pausa pueden modificarse, lo que nos permite un ajuste fino. En las pruebas, los resultados fueron bastante adecuados, como podrás comprobar en el video a continuación. Ahí podrás ver el tiempo que el sistema logró entre las barras de 1 minuto.

Ten en cuenta que la precisión no es perfecta, pero puedes ajustar los valores para obtener un tiempo más preciso. Recuerda que no es posible utilizar el contador interno del sistema operativo para lograr este tiempo exacto, ya que dicho contador no puede trabajar con tiempos inferiores a 16 milisegundos con una buena precisión. Ten en cuenta que este trabajo es de investigación y aún no está finalizado. Puede llevar algún tiempo encontrar una forma de mejorar aún más las cosas, pero por ahora, creo que es suficiente.



Pero también debemos verificar si el gráfico sigue abierto. Esto se hace de vez en cuando. Sin embargo, dado que lo haremos mucho menos veces por segundo, la demora causada será mucho menor. Ten en cuenta este hecho: cualquier llamada generará siempre una pequeña demora. Así como también necesitamos actualizar el valor de la posición y capturar el estado actual del indicador de control. Esto también generará una pequeña demora en la ejecución. Además, la verificación de si el gráfico está o no abierto causará cierta pérdida de rendimiento. Pero esta será mucho menor precisamente porque la llamada se ejecuta pocas veces por segundo.

Con esto, podemos concluir esta parte por ahora. Pero antes, veamos las otras dos rutinas que forman parte de este sistema de construcción de las barras de 1 minuto. La primera rutina es la que buscará el punto en el que la repetición/simulación debe iniciarse.

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }

Esta no ha experimentado grandes cambios con respecto a la versión anterior. Sin embargo, a diferencia de esa versión, en esta ya no devolveremos ningún valor. La creación de las barras se ha modificado para ser más eficiente. Puede parecer insignificante, pero el simple hecho de hacerlo ayudará mucho. Ahora, hay un pequeño detalle más: Esta rutina anterior ya no es una rutina pública. Ya no se puede acceder a ella fuera de nuestra clase objeto. Al igual que la próxima rutina, que será precisamente la responsable de crear las barras de 1 minuto.

inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Esta rutina se explica prácticamente por sí sola. Lo que tenemos aquí es la creación de las barras de 1 minuto, una por una, para luego enviarlas al gráfico. Es cierto que aún no estamos trabajando con los ticks, es decir, todavía no podemos utilizar ciertos recursos de MetaTrader 5 para la repetición/simulación. Esto se implementará en el futuro. Así que no te preocupes por estos detalles por ahora. Al mismo tiempo que hacemos esto, también realizamos todas las pruebas y mediciones necesarias para determinar si es necesario comenzar a construir una nueva barra o no. Esta área puede ser omitida si deseas hacerlo en el futuro, ya que todo lo que hace es mostrar las métricas de tiempo entre una barra y la anterior. En este momento, esto es bastante útil para ayudarnos a ajustar finamente los valores de retraso dentro del bucle de creación.

Con esto, terminamos esta parte de la presentación de las barras de 1 minuto. Ahora que el sistema puede presentarlas en un tiempo bastante aceptable, al menos en lo que respecta a los ticks negociados y simulados con los que se realizaron las pruebas. Ahora abordaremos otro problema relacionado con los ticks simulados. En cuanto a los ticks reales, no tenemos ningún problema adicional en este momento.


Visualización del gráfico RANDOM WALK

Hasta ahora, el trabajo ha sido interesante e incluso agradable de realizar. Sin embargo, ahora nos enfrentamos a algo que para algunos puede resultar realmente complicado. Pero es algo que realmente debe hacerse: simular la emisión de todos los ticks que pueden estar presentes de alguna manera en las barras de 1 minuto. Para que comprendas lo que realmente está sucediendo, te sugiero que estudies todo el contenido que te mostraré. Y para no complicar demasiado las cosas, aquí no mostraré la versión final del sistema de simulación. Esa versión final se mostrará más adelante. La razón es que todo este asunto es bastante denso para mostrarse de una sola vez.

Lo que realmente queremos y vamos a producir es lo que se llama RANDOM WALK (paseo aleatorio). Este "paseo de borracho" tiene algunas reglas. A diferencia de lo que se programa normalmente, aquí no podemos permitir que el sistema sea completamente aleatorio. Necesitamos crear algunas reglas matemáticas para tratar de dirigir el movimiento. No me malinterpretes, el paseo aleatorio es, de hecho, un movimiento completamente aleatorio e impredecible, al menos a corto plazo. Pero debido a que no estamos creando un movimiento completamente impredecible, ya que sabemos dónde comienza y dónde termina, el sistema no es completamente aleatorio. Aunque, aun así, haremos que algo de aleatoriedad ocurra dentro de las barras.

Existen algunas ideas que se pueden utilizar para facilitar la creación de un movimiento realmente de paseo aleatorio. Sin embargo, algunas aproximaciones son mejores que otras, dependiendo del caso específico. Un programador con poca experiencia podría pensar que sería suficiente utilizar un generador de números aleatorios y realizar algún tipo de conversión para limitar los valores a un rango determinado. Esta aproximación, aunque no parece del todo incorrecta, tiene algunas deficiencias. Si observaras los datos generados por dicho movimiento en un gráfico, obtendrías algo similar a la imagen a continuación:

Puede que estés pensando que este gráfico (SÍ, es un gráfico, que más adelante te mostraré cómo crear), no se parece en absoluto a un movimiento aleatorio. Parece más bien un gran desorden. Pero en realidad, es un movimiento aleatorio, que se logra utilizando saltos entre los puntos en el tiempo. Para lograr este movimiento, utilizaremos la siguiente línea de datos obtenidos con la ayuda de la plataforma MetaTrader 5. Recuerda que cada línea representa una barra de 1 minuto.

Estos datos estarán disponibles en los archivos adjuntos para que puedas realizar tus propios análisis más adelante. Pero continuemos para comprender por qué el gráfico anterior es tan inesperadamente diferente de lo que cabría esperar. Para entender esto, será necesario que sepas cómo se creó. Para empezar, dentro del archivo de servicio, vamos a definir dos cosas. Estas se utilizarán de manera temporal y no volverán a aparecer en el código fuente en el futuro.

#define def_TEST_SIMULATION
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

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

Esta definición nos permitirá generar una prueba de simulación para que podamos analizar el gráfico del movimiento generado. Por otro lado, este archivo contendrá los datos correspondientes al movimiento simulado dentro de una barra de 1 minuto. Más adelante veremos dónde y cuándo se creará este archivo. Una vez que esto esté definido directamente en el archivo de servicio, antes de declarar los archivos de encabezado, podremos utilizar esta definición en los archivos MQH. Ahora, vamos al archivo C_Replay.Mqh para entender qué haremos para obtener los datos.

Para capturar realmente cómo el simulador creó el movimiento dentro de la barra de 1 minuto y entenderlo, utilizaremos la siguiente función.

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
                                int file;
                                MqlRates rate[1];
                                MqlTick tick[];
                                
                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Convertendo barras em ticks. Aguarde...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                Simulation(rate[0], tick);
#ifdef def_TEST_SIMULATION
        FileClose(file);
        file = FileOpen(def_FILE_OUT_SIMULATION, FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%0.f\n", m_Ticks.Info[c0].last));
        FileClose(file);
        ArrayFree(tick);
        
        return false;
#endif
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }

Un detalle que algunos, menos experimentados, podrían estar preguntándose es si el simulador siempre realizará el mismo tipo de simulación de movimiento dentro de la barra de 1 minuto. En realidad, NO. Vamos a intentar crear una forma en la que cada barra sea única y tenga un movimiento exclusivo. Sin embargo, si deseas que el movimiento sea siempre idéntico o similar entre las barras, simplemente debes forzar al sistema a una condición en la que siempre inicie el simulador desde un valor determinado. La forma de hacerlo es agregar una llamada antes de la llamada al simulador y establecer un valor fijo en esta llamada, como se muestra a continuación:

// ... Código ...

                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Convertendo barras em ticks. Aguarde...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                srand(5);
                                                Simulation(rate[0], tick);

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

Pero volvamos a nuestro código original. Debido a que tendremos una definición en el archivo de servicio, cuando la ejecución alcance este punto, crearemos un archivo definido allí en el servicio, que contendrá todo el contenido presente en la matriz de ticks, que en este caso es una matriz simulada. Esto ocurrirá independientemente de lo que esté sucediendo dentro de la función de simulación. De esta manera, podremos utilizar EXCEL para verificar gráficamente lo que sucedió. Detalle: Al hacer esto, recibirás un mensaje de error por parte del servicio. Pero este mensaje debe ser ignorado cuando la definición de prueba esté presente. Otro punto importante que debes saber es que utilizaremos EXCEL porque es más sencillo para generar el gráfico. Podríamos utilizar MetaTrader 5 para hacerlo, pero el resultado sería muy confuso debido a la cantidad de información generada; es más sencillo visualizarlo en EXCEL. Nada impide que utilices otro programa para generar el gráfico. Lo importante es que generes y observes el gráfico creado por el simulador. Si no sabes cómo hacerlo en EXCEL, consulta el video a continuación, donde te muestro cómo hacerlo.



Es de suma importancia que sepas cómo crear el gráfico, ya que necesitarás de alguna manera verificar si el movimiento se está creando de manera adecuada. Observar simplemente el movimiento de las barras que se están creando no será suficiente para que determines si realmente tiene el nivel de aleatoriedad adecuado. Otra cosa muy común entre los programadores es que intentar complicar los cálculos realizados dentro del procedimiento de simulación no asegura que efectivamente tengamos un movimiento verdaderamente aleatorio y similar al RANDOM WALK. Entonces, mira el vídeo. Es corto y te ayudará mucho en los próximos pasos, además de otras cosas. Ya que solo mostraré el gráfico obtenido. Debes comprender por qué el gráfico tiene esa apariencia específica. Esto te permitirá realizar pruebas locales.


Conclusión

Para no complicarte ni confundirte, querido lector, con respecto a lo que veremos en la implementación de un modelo RANDOM WALK, voy a concluir este artículo en este punto. En el próximo artículo, implementaremos un modelo RANDOM WALK, donde el gráfico generado tendrá un aspecto diferente. También abordaremos los problemas que este 'caminar libre' nos presenta y la idea que hay detrás de ello. Pero como ya tenemos algo que es nuevo para muchos y bastante complicado para otros, no quiero complicar las cosas aún más. A pesar de que en el código adjunto tendrás acceso a algo que solo explicaré en el próximo artículo.

No tengas prisa. Primero estudia y comprende lo que se explicó en este artículo. Porque sin este conocimiento y una comprensión adecuada de este contenido, no podrás entender absolutamente nada de lo que se hará pronto. 

Un detalle: Para compilar el servicio y hacer que el sistema de repetición/simulación funcione, es decir, para que deje de generar datos para el análisis del gráfico, desactiva la directiva de prueba. De la misma manera en que se muestra en el código a continuación. Esto se refiere al archivo de servicio.

//#define def_TEST_SIMULATION // <<-- Deixe esta linha desta forma para poder utilizar o serviço de replay / simulador ....
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

Si no dejas las cosas como se muestra arriba, el servicio siempre reportará un error cuando intentes ver más datos además del gráfico. ASÍ QUE presta atención a este detalle.

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

Archivos adjuntos |
Teoría de categorías (Parte 9): Acciones de monoides Teoría de categorías (Parte 9): Acciones de monoides
El presente artículo continúa la serie sobre la implementación de la teoría de categorías en MQL5. En este artículo examinaremos las acciones de los monoides como un medio de transformación de los monoides descritos en el artículo anterior para aumentar sus aplicaciones.
Redes neuronales: así de sencillo (Parte 43): Dominando las habilidades sin función de recompensa Redes neuronales: así de sencillo (Parte 43): Dominando las habilidades sin función de recompensa
El problema del aprendizaje por refuerzo reside en la necesidad de definir una función de recompensa, que puede ser compleja o difícil de formalizar. Para resolver esto, se están estudiando enfoques basados en la variedad de acciones y la exploración del entorno que permiten aprender habilidades sin una función de recompensa explícita.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 14): Nacimiento del SIMULADOR (IV) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 14): Nacimiento del SIMULADOR (IV)
En este artículo, continuaremos con la fase de desarrollo del simulador. Sin embargo, ahora veremos cómo crear efectivamente un movimiento del tipo "RANDOM WALK" (paseo aleatorio). Este tipo de movimiento es bastante intrigante, ya que sirve de base para todo lo que sucede en el mercado de capitales. Además, comenzarás a comprender algunos conceptos esenciales para quienes realizan análisis de mercado.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 12): Nacimiento del SIMULADOR (II) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 12): Nacimiento del SIMULADOR (II)
Desarrollar un simulador puede resultar mucho más interesante de lo que parece. Así que demos algunos pasos más en esta dirección, porque las cosas están empezando a ponerse interesantes.