English Русский 中文 Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 17): Ticks y más ticks (I)

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 17): Ticks y más ticks (I)

MetaTrader 5Ejemplos | 26 octubre 2023, 14:59
301 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 16): Un nuevo sistema de clases", se realizaron los cambios necesarios en la clase C_Replay. Estos cambios tienen como objetivo simplificar varias tareas que necesitaremos realizar. Así que la clase C_Replay, que antes era una clase grande y abultada, ha pasado por un proceso de simplificación en el que su complejidad se ha distribuido entre otras clases. De esta manera, se ha vuelto mucho más sencilla y fácil de implementar nuevas funcionalidades y mejoras en el sistema de repetición/simulación. Estas mejoras comenzarán a ser vistas a partir de este artículo y se extenderán a otros siete artículos que vendrán a continuación.

La primera de las cuestiones que abordaremos es extremadamente complicada de modelar de una manera que todos puedan entender simplemente estudiando el código. Sabiendo esto, me gustaría que tú prestaras la debida atención a las explicaciones que se darán a lo largo de estos artículos. Si haces esto, podrás seguir el tema, al menos eso es lo que espero que suceda, porque el tema es realmente denso y complejo. Digo esto porque lo que haremos a partir de este artículo puede ser innecesario en algunos casos desde el punto de vista de algunos, pero para otros será de suma importancia. A pesar de ser algo bastante costoso de hacer, lo haré y presentaré los detalles en etapas para que puedas seguir el razonamiento y no te sientas perdido.

La gran cuestión es que, durante todos los artículos anteriores, el enfoque fue exclusivamente en la generación de un gráfico. Y este gráfico debería ser representado de tal manera que el activo de repetición/simulación se comporte de una manera bastante similar a lo que sucede en el mercado real. Sé que hay muchas personas que operan utilizando algunas otras herramientas, como por ejemplo el libro de órdenes. A pesar de que personalmente no considero buena práctica utilizar dicho instrumento, muchos creen que existe alguna correlación entre lo que sucede en el libro de órdenes y lo que se negocia. Está bien, cada uno ve las cosas como quiere. Pero independientemente de esto, sí existe una herramienta que muchos utilizan en sus operaciones, que es el gráfico de ticks. Si no sabes de qué se trata, echa un vistazo a la imagen de la figura 01.


Figura 01

Figura 01 - Gráfico de Ticks

Este gráfico aparece en varios lugares en la plataforma MetaTrader 5. Para que tengas una idea de los lugares, puedo mencionar algunos que forman parte de la versión estándar de MetaTrader 5. Estos son: la ventana de Observación de mercado, como se puede ver en la figura 01. En el libro de órdenes (figura 02) y también en el sistema de órdenes (figura 03).

Además de estos lugares, tú también puedes utilizar algún indicador para ver esta misma información. Un ejemplo de esto se puede encontrar en "Desarrollando un EA comercial desde cero (Parte 13): Times And Trade (II)". Todos estos sistemas requieren que el sistema que estamos desarrollando pueda informar o transmitir la información de los ticks de una manera adecuada. Pero no es precisamente la información sobre los ticks que ves en realidad en todas estas figuras. Lo que estás viendo de hecho es la variación en los valores de precio ASK y BID. Eso es lo que realmente se está mostrando. 


Figura 02

Figura 02 - Gráfico de ticks visto en el libro de órdenes


Figura 03

Figura 03 - Gráfico de ticks visto en el sistema de órdenes


Es importante que te concentres en entender este hecho. Pero independientemente de esto, no quiero que el sistema que estamos desarrollando carezca de esta información. La razón de esto es promover una experiencia lo más cercana posible a un mercado real. Y esta información es de alguna manera válida y debe estar presente, aunque el usuario del sistema no la utilice realmente. No quiero que él piense que no es posible desarrollar algo así. Es cierto que hacerlo no es una de las tareas más simples. Para ser honesto, la tarea es mucho más complicada de lo que parece y pronto entenderás por qué. Esto se irá aclarando a medida que se proporcionen explicaciones. Veremos cuán complicada es esta tarea y llena de pequeños detalles, algunos de ellos bastante peculiares, por así decirlo.

Aquí comenzaremos a implementar este sistema, pero lo haremos de la manera más simple posible. En primer lugar, haremos que aparezca en la ventana de Observación de mercado (Figura 01). A partir de ahí, intentaremos que también aparezca en otros lugares. Pero el simple hecho de hacer que aparezca en la ventana de Observación de mercado será una tarea bastante complicada. Al mismo tiempo, será interesante, ya que cuando se haga y estemos utilizando la simulación de movimientos dentro de un intervalo de 1 minuto, podremos observar en el gráfico de ticks en la ventana de Observación de mercado cómo el RANDOM WALK fue creado por el simulador. Esto es algo bastante curioso e interesante de ver, de hecho.

Pero vayamos con calma. A pesar de que la cosa parezca ser simple de construir, no encontré referencias que realmente pudieran ayudarme en la implementación, para simplificar las cosas o dar algún paso a seguir. Básicamente, la única referencia que encontré buscando en varios lugares fue la documentación de MQL5, y nada más allá de eso. Incluso esta documentación no aclara algunos detalles. Lo que expondré aquí, en este y los próximos artículos, es lo que realmente logré entender al implementar el sistema. Disculpen a aquellos que puedan entender el sistema de manera diferente o tengan más experiencia en el tema. A pesar de mis intentos, la única forma real de hacer que la cosa funcione fue la que se mostrará. Entonces, consejos o sugerencias de otras formas serán muy bienvenidos, siempre que sean realmente funcionales.

Comencemos a implementar la cosa más loca de todas debido al grado de locura involucrado. En este sistema que se implementará, en estos primeros momentos, no utilizaremos datos simulados. Por lo tanto, en el archivo adjunto, encontrarás datos REALES de 2 días en 4 activos diferentes. Esto es para que tengas al menos una base para experimentar. No necesitas confiar en mí, todo lo contrario. Quiero que captures los datos reales del mercado tú mismo y los pruebes en el sistema. De esta manera, podrás sacar tus propias conclusiones sobre lo que realmente está sucediendo. Esto antes de que implementemos un sistema de simulación. Porque en realidad, la cosa es mucho más loca de lo que podría parecer en este primer momento.


Implementación de la primera versión

En esta primera versión, algunos de los recursos estarán desactivados. Esto se debe a que no quiero que simplemente creas que el código está completamente correcto. En realidad, hay un defecto en él, que se refiere al temporizador. Esto se puede notar al probar los datos reales que se adjuntarán. Pero en este momento, es algo que podemos pasar por alto, ya que no causa ningún perjuicio al proceso en sí. Solo hace que el tiempo de construcción de las barras de 1 minuto no sea exactamente igual que en el mercado real.

Pero vamos a empezar haciendo un pequeño cambio en el archivo del servicio:

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.17"
#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 link "https://www.mql5.com/pt/articles/11106"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Mini Indice.txt";     //Arquivo de configuração do Replay.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;             //Tempo gráfico inicial.
//input bool            user02 = false;                 //Visualizar a construção das barras. ( Bloqueado Temporariamente )
input bool              user03 = true;                  //Visualizar métricas de criação.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        *pReplay;

        pReplay = new C_Replay(user00);
        if ((*pReplay).ViewReplay(user01))
        {
                Print("Permissão concedida. Serviço de replay já pode ser utilizado...");
                while ((*pReplay).LoopEventOnTime(false, user03));
        }
        delete pReplay;
}
//+------------------------------------------------------------------+

Esta línea se mantuvo bloqueada debido a que había algunos detalles que necesitaban ser modificados para que el proceso de visualización de la construcción de las barras se realice de manera adecuada. Por esta razón, el proceso de visualización, en caso de un avance rápido, no será visible. Para garantizar esto, pasamos el argumento correspondiente como falso, o cero.

Esta es la primera cosa que necesitamos hacer. Ahora tendremos que hacer algunos otros pequeños cambios. A partir de ahora, las cosas pueden empezar a volverse un poco confusas para aquellos que están viendo este artículo antes de haber leído los demás. Si eso sucede, te aconsejo que detengas la lectura de este artículo por el momento y comiences leyendo desde el primer artículo de esta serie: "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 01): Primeros experimentos (I)", porque comprender lo que estaba ocurriendo te ayudará a entender lo que sucederá a partir de este punto.

Dado este consejo, vamos a seguir adelante. Lo primero que haremos ahora será cambiar la rutina de lectura del archivo que contiene los ticks reales. La rutina original se puede ver a continuación.

inline bool ReadAllsTicks(void)
                        {
#define def_LIMIT (INT_MAX - 2)
                                string   szInfo;
                                MqlTick  tick;
                                MqlRates rate;
                                int      i0;
                                
                                Print("Carregando ticks de replay. Aguarde...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                i0 = m_Ticks.nTicks;
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        tick.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        tick.bid = StringToDouble(FileReadString(m_File));
                                        tick.ask = StringToDouble(FileReadString(m_File));
                                        tick.last = StringToDouble(FileReadString(m_File));
                                        tick.volume_real = StringToDouble(FileReadString(m_File));
                                        tick.flags = (uchar)StringToInteger(FileReadString(m_File));
                                        if ((m_Ticks.Info[i0].last == tick.last) && (m_Ticks.Info[i0].time == tick.time) && (m_Ticks.Info[i0].time_msc == tick.time_msc))
                                                m_Ticks.Info[i0].volume_real += tick.volume_real;
                                        else
                                        {
                                                m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                if (tick.volume_real > 0.0)
                                                {
                                                        ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                                        m_Ticks.nTicks++;
                                                }
                                                i0 = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : i0);
                                        }
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Excesso de dados no arquivo de tick.\nNão é possivel continuar...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_LIMIT
                        }

Pero notarás que algunas partes han sido eliminadas del código. Así que el código final se ve como se muestra a continuación: Esta es la nueva rutina de lectura de los ticks reales.

inline bool ReadAllsTicks(const bool ToReplay)
                        {
#define def_LIMIT (INT_MAX - 2)
#define def_Ticks m_Ticks.Info[m_Ticks.nTicks]

                                string   szInfo;
                                MqlRates rate;
                                
                                Print("Carregando ticks de replay. Aguarde...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        def_Ticks.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        def_Ticks.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        def_Ticks.bid = StringToDouble(FileReadString(m_File));
                                        def_Ticks.ask = StringToDouble(FileReadString(m_File));
                                        def_Ticks.last = StringToDouble(FileReadString(m_File));
                                        def_Ticks.volume_real = StringToDouble(FileReadString(m_File));
                                        def_Ticks.flags = (uchar)StringToInteger(FileReadString(m_File));
                                        if (def_Ticks.volume_real > 0.0)
                                        {
                                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                m_Ticks.nRate += (BuiderBar1Min(rate, def_Ticks) ? 1 : 0);
                                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        }
                                        m_Ticks.nTicks++;
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Excesso de dados no arquivo de tick.\nNão é possivel continuar...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_Ticks
#undef def_LIMIT
                        }

Observa que ahora ya no ignora los valores contenidos en las posiciones BID y ASK. Además, ya no acumula los valores si la posición lo permite. Es decir, está leyendo los datos íntegramente y los está almacenando en la memoria. Debido a que los cambios no generaron ningún nuevo procedimiento dentro de la rutina. Más bien, la simplificaron. Creo que tú, que estás siguiendo esta serie de artículos, no tendrás dificultades para entender lo que realmente está sucediendo. Pero el hecho de haber realizado estas simplificaciones tiene consecuencias en otros puntos del código. Algunos de estos puntos sufrirán bastante. Por eso fue necesario deshabilitar algunas cosas hasta que todo el código vuelva a funcionar realmente de manera estable.

Podríamos hacer los cambios, estabilizar el código y mostrar la versión final de inmediato. Pero creo que mostrar el paso a paso de cómo se hacen los cambios será de gran valor para aquellos que están aprendiendo y realmente desean entender cómo funcionan las cosas en detalle. Además, hay otra razón para explicar estos cambios. Pero sobre todo, hacer las cosas con calma y sin sobresaltos hará que el aprendizaje de los detalles, que a menudo son mal comprendidos, sea más accesible. Y lo que es peor, muchos de estos detalles son mal explicados por aquellos que afirman ser operadores profesionales. Esto considerando a aquellos que dicen realmente vivir de operar en el mercado financiero. Pero tales cuestiones están más allá del alcance de esta serie de artículos. No nos desviemos de nuestro enfoque principal. Así que sigamos implementando las cosas poco a poco. Porque de esta manera, todo tendrá más sentido más adelante. Sobre todo cuando hablemos de otro mercado también bastante curioso e interesante. Pero no quiero arruinar la sorpresa. Sigue leyendo los artículos y verás de qué estoy hablando.

Una vez realizados estos primeros cambios, necesitaremos hacer otra alteración. Esta es un poco más extraña, pero igualmente necesaria. Ahora que tendremos valores donde no existe volumen alguno (valores de BID y ASK), debemos hacer que el sistema comience en un punto donde tengamos algún volumen indicado.

class C_ConfigService : protected C_FileTicks
{
        protected:
//+------------------------------------------------------------------+
                datetime m_dtPrevLoading;
                int      m_ReplayCount;
//+------------------------------------------------------------------+
inline void FirstBarNULL(void)
                        {
                                MqlRates rate[1];
                                
                                for(int c0 = 0; m_Ticks.Info[c0].volume_real == 0; c0++)
                                        rate[0].close = m_Ticks.Info[c0].last;
                                rate[0].open = rate[0].high = rate[0].low = rate[0].close;
                                rate[0].tick_volume = 0;
                                rate[0].real_volume = 0;
                                rate[0].time = m_Ticks.Info[0].time - 60;
                                CustomRatesUpdate(def_SymbolReplay, rate);
                                m_ReplayCount = 0;
                        }
//+------------------------------------------------------------------+

//... Restante da classe ...

}

Originalmente, esta rutina era privativa de la clase y no tenía los puntos destacados. Pero junto con ella, que ahora es una rutina protegida, también tenemos una variable. Esta variable, por casualidad, es precisamente la que se utiliza en el contador del replay. Esta variable está aquí únicamente para que su valor sea modificado por esta rutina específica, y solo por ella. Este bucle hará que la barra inicial que se encuentra en el extremo izquierdo del gráfico tenga un valor adecuado. Recuerda: Ahora tenemos los valores BID y ASK junto con los valores de precio. Sin embargo, los valores de BID y ASK no tienen ningún significado para nosotros, al menos no en este momento.

Hasta aquí, todo es bastante simple y fácil de entender. Ahora vamos a entrar en la clase responsable de la repetición del mercado. Esta parte contiene algunas cosas bastante extrañas que pueden no tener mucho sentido a primera vista. Así que veamos esto en el próximo apartado.


Modificación de la clase C_Replay

Los cambios aquí comienzan de manera más sencilla y se vuelven bastante extraños. Comencemos con la parte fácil de los cambios, que se puede ver a continuación:

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (m_ReplayCount == 0)
                                        for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
                                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)) FirstBarNULL(); 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 rutina aún no está realmente finalizada. Debido a esto, hemos tenido que bloquear la visualización del sistema de construcción de barras de 1 minuto. Pero incluso sin estar completamente finalizada, fue necesario agregar un código adicional en la misma. Este código hace algo muy parecido a lo que ocurre cuando colocamos la barra en el extremo izquierdo del gráfico. Muy probablemente, uno de los códigos desaparecerá en futuras versiones. Pero este, además de realizar ese trabajo, hace algo aún más sutil. En realidad, evita que al iniciar la repetición/simulación, el activo dé un salto antes de que se trace la primera barra. Si deshabilitas esta línea de código, verás que al comienzo del trazado, ocurre un salto. Este salto se debe a otro factor, que es lo próximo a ver.

Para explicar cómo realmente fue posible y es posible agregar los ticks en la ventana de observación de mercado, es necesario ver la rutina original de creación de las barras. Puedes verla a continuación:

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 original es responsable únicamente de la creación de las barras que se trazan en el gráfico. Quiero que prestes mucha atención al código de arriba y lo compares con el siguiente código:

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

                                bool bNew;

                                if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
                                {                               
                                        if (bViewMetrics) Metrics();
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                bNew = (def_Rate.tick_volume == 0);
                                def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
                                def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (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;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
                                ViewTick();
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Parece ser lo mismo, pero no lo es. Hay diferencias. Y no, no se trata de que este segundo código cuenta con dos nuevas llamadas. En realidad, la primera llamada está ahí solo porque decidí quitar el código de métricas de la rutina. El código de métricas se puede ver a continuación. Es exactamente lo que estaba en la rutina original.

inline void Metrics(void)
                        {
                                int i;
                                static ulong _mdt = 0;
                                
                                _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();
                                
                        }

En realidad, la mayor diferencia radica en cómo el sistema encuentra el precio de cierre de la barra. Cuando no había interferencia de los valores BID y ASK, era bastante sencillo saber cuál era el valor que debía utilizarse como precio de cierre. Pero con el BID y el ASK interfiriendo en la cadena de datos, necesitamos una forma diferente de hacerlo. Y es precisamente mirando si la posición tiene o no algún volumen de negociación que sabemos si es o no un valor que puede ser utilizado como precio de cierre.

Esta es la cuestión clave en esta nueva rutina. Además, todavía contamos con dos llamadas: la primera ya la he mostrado. Ahora, la segunda, es donde la cosa realmente se vuelve bastante extraña.

El código de esta segunda llamada se puede ver a continuación:

inline void ViewTick(void)
                        {
                                MqlTick tick[1];

                                tick[0] = m_Ticks.Info[m_ReplayCount];
                                tick[0].time_msc = (m_Ticks.Info[m_ReplayCount].time * 1000) + m_Ticks.Info[m_ReplayCount].time_msc;
                                CustomTicksAdd(def_SymbolReplay, tick);
                        }

Este código puede parecer absolutamente extraño, pero sin embargo, funciona. La razón de esto se puede encontrar en la documentación sobre cómo utilizar la función CustomTicksAdd. No quiero generar controversia aquí, así que utilizaré exactamente el contenido presente en la documentación antes de explicar por qué esta rutina anterior funciona y por qué debe ser exactamente así.

A continuación, se presenta el contenido de la documentación:

Nota adicional

La función CustomTicksAdd solo funciona para símbolos personalizados abiertos en la ventana "Observación del mercado". Si el símbolo no está seleccionado en "Observación del mercado", debes agregar ticks usando CustomTicksReplace.

La función CustomTicksAdd permite transmitir ticks como si llegaran desde el servidor del bróker. Los datos no se guardan directamente en la base de datos de ticks, en cambio, se envían a la ventana "Observación del mercado". Desde allí, el terminal almacena los ticks en su base de datos. Cuando la cantidad de datos transferidos por llamada es grande, la función cambia su comportamiento para ahorrar recursos. Si se transfieren más de 256 ticks, los datos se dividen en dos partes. La primera parte (grande) se carga inmediatamente en la base de datos de ticks (como lo hace CustomTicksReplace). La segunda parte, que consta de los últimos 128 ticks, se transfiere a la ventana "Observación del mercado" y luego el terminal la guarda en la base de datos de ticks.

La estructura MqlTick tiene dos campos de valores temporales, es decir, time (hora del tick en segundos) y time_msc (hora del tick en milisegundos). Realizan el cálculo desde el 01 de enero de 1970. En los ticks agregados, el procesamiento de estos campos se realiza de acuerdo con las siguientes reglas en el orden establecido:

  1. Si el valor de ticks[k].time_msc no es igual a cero, lo utilizamos para llenar el campo ticks[k].time, es decir, para el tick se define el tiempo ticks[k].time=ticks[k].time_msc/1000 (división entera).
  2. Si ticks[k].time_msc es igual a cero y ticks[k].time no es igual a cero, se obtiene el tiempo en milisegundos multiplicándolo por 1000, es decir, ticks[k].time_msc=ticks[k].time*1000.
  3. Si ticks[k].time_msc es igual a cero y ticks[k].time es igual a cero, en estos campos se registra el tiempo actual del servidor comercial en milisegundos cuando se llama a la función CustomTicksAdd.

Si el valor de los campos ticks[k].bid, ticks[k].ask, ticks[k].last o ticks[k].volume es mayor que cero, en el campo ticks[k].flags se escribe la combinación de las flags correspondientes:

  • TICK_FLAG_BID — el tick cambió el precio Bid.
  • TICK_FLAG_ASK — el tick cambió el precio Ask.
  • TICK_FLAG_LAST — el tick cambió el precio de la última transacción.
  • TICK_FLAG_VOLUME — el tick cambió el volumen.

Si el valor del campo es menor o igual a cero, la flag correspondiente no se escribe en el campo ticks[k].flags. 

Las flags TICK_FLAG_BUY y TICK_FLAG_SELL no se agregan al historial del instrumento personalizado.

El detalle de esta nota es que, para muchas personas, puede no tener mucho sentido. Pero es precisamente lo que estoy utilizando para que las cosas funcionen. Aquí se especifican las condiciones en las que: el tiempo en milisegundos es diferente de cero; el tiempo en milisegundos es igual a cero y el tiempo del tick es diferente de cero; y cuando tanto el tiempo en milisegundos como el tiempo del tick son iguales a cero. El gran problema es que cuando utilizamos ticks reales de un archivo, estas condiciones no son tan claras para la gran mayoría. Y esto nos lleva a un problema. Si alguien intenta utilizar los ticks reales obtenidos de un archivo para intentar insertar estos datos en la información de ticks, no obtendrá el resultado deseado.

Por esta razón, muchos pueden estar intentando realizar este modelado, pero no están logrando hacerlo. Esto se debe simplemente a que no están entendiendo la documentación. Pero utilizando precisamente este hecho (que está implícito en la documentación) es que he creado el código que se muestra arriba. En él, estoy forzando la primera de las condiciones. Es decir, donde el valor del tiempo en milisegundos es diferente de cero. Pero debes tener en cuenta que el valor que indica el tiempo en milisegundos también debe contener el valor del tiempo. Ya que MetaTrader 5 realizará un cálculo para generar el valor del tiempo. Por lo tanto, necesitamos ajustar las cosas en el valor informado en el campo de milisegundos.

De esta manera, la función CustomTicksAdd podrá insertar los datos en la Observación del Mercado. Pero no es solo eso, al insertar estos datos en el sistema, también aparecerán las líneas de precio de BID, precio ASK y la línea del último precio en el gráfico que se está trazando. En resumen, como bonificación por haber logrado insertar los ticks en la Observación del Mercado, también obtuvimos las líneas de precio en el gráfico. Esto no estaba presente debido a la falta de este tipo de funcionalidad. Sin embargo, no celebres todavía, ya que el sistema no está completo. Todavía hay algunas cosas que deben ser revisadas, corregidas y ensambladas. Es por eso que estoy utilizando y proporcionando datos de TICKS REALES para que puedas experimentar esta nueva fase del sistema de repetición/simulación.


Últimas reflexiones

Este artículo llega a su fin en este punto, ya que las cosas que aún deben hacerse generarán mucha confusión en la explicación que ya se ha proporcionado. De esta manera, en el próximo artículo, mostraré cómo corregir algunas cosas que no funcionan adecuadamente en este sistema actual. A pesar de todo, puedes utilizar el sistema sin realizar avances o retrocesos rápidos. Si lo haces, es posible que los datos de los ticks en la Observación del Mercado o la información de las líneas de precios no coincidan con la situación actual en el gráfico de repetición/simulación.

Para no dar preferencia solo al uso de contratos como el mini índice, quiero que pruebes este sistema en otros activos. Esto hará que quede más claro cómo se comportará el sistema de repetición/simulación en relación a lo que estamos implementando en él. Solo quiero dejar claro un hecho. Todavía hay algunas fallas en el sistema de desplazamiento rápido. Por lo tanto, te sugiero que, al menos por ahora, evites el uso del avance rápido.

En estas pruebas que te sugiero realizar, quiero que prestes la debida atención tanto a la liquidez como a la volatilidad del activo que elijas. Observa las métricas en más de un activo diferente. Ten en cuenta que en activos con un número menor de operaciones realizadas en el intervalo de 1 minuto, el sistema de repetición/simulación parece tener dificultades para trabajar con ellos. De alguna manera, es bueno ver esto en este momento, ya que necesita ser corregido. Aunque la construcción de las barras aparentemente es correcta. Haremos esta corrección pronto. Pero quiero que tú, querido lector, entiendas por qué el servicio de repetición/simulador parece extraño antes de corregir esta falla. Este entendimiento es importante si realmente deseas profundizar en la programación. No te quedes siempre en la creación de programas simples y fáciles. Los verdaderos programadores son aquellos que resuelven problemas cuando surgen, no aquellos que se rinden al primer signo de dificultad.

Sin embargo, al observar el tiempo, tanto en la ventana de Observación del Mercado como en el valor proporcionado por el sistema de métricas, el servicio de repetición/simulador no logra sincronizar adecuadamente el sistema cuando los tiempos son mayores a 1 segundo. Necesitamos y vamos a corregir esto pronto. Pero mientras tanto, estudia este código, ya que realmente valdrá la pena en términos de aprendizaje y de cómo trabajar con los ticks en la ventana de Observación del Mercado. Nos vemos en el próximo artículo. La cosa se pondrá aún más emocionante.


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

Archivos adjuntos |
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 18):  Ticks y más ticks (II) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 18): Ticks y más ticks (II)
En este caso, es extremadamente claro que las métricas están muy lejos del tiempo ideal para la creación de barras de 1 minuto. Entonces, lo primero que realmente corregiremos es precisamente esto. Corregir la cuestión de la temporización no es algo complicado. Por más increíble que parezca, en realidad es bastante simple de hacer. Sin embargo, no realicé la corrección en el artículo anterior porque allí el objetivo era explicar cómo llevar los datos de los ticks que se estaban utilizando para generar las barras de 1 minuto en el gráfico a la ventana de observación del mercado.
DoEasy. Elementos de control (Parte 32): "ScrollBar" horizontal, desplazamiento con la rueda del ratón DoEasy. Elementos de control (Parte 32): "ScrollBar" horizontal, desplazamiento con la rueda del ratón
En este artículo completaremos el desarrollo de la funcionalidad del objeto de barra de desplazamiento horizontal. Asimismo, haremos posible el desplazamiento del contenido del contenedor moviendo el control deslizante de la barra de desplazamiento y girando la rueda del ratón. También introduciremos ciertas adiciones a la biblioteca considerando la nueva política de ejecución de órdenes aparecida en el terminal y los nuevos códigos de error de ejecución en MQL5.
Programación orientada a objetos (OOP) en MQL5 Programación orientada a objetos (OOP) en MQL5
Como desarrolladores, debemos aprender a crear y desarrollar software que sea reutilizable y flexible sin duplicar código, especialmente si tenemos diferentes objetos con comportamientos distintos. Esto se puede lograr fácilmente utilizando las técnicas y principios de la programación orientada a objetos. En este artículo le presentamos los conceptos básicos de la programación orientada a objetos en MQL5.
Redes neuronales: así de sencillo (Parte 47): Espacio continuo de acciones Redes neuronales: así de sencillo (Parte 47): Espacio continuo de acciones
En este artículo ampliamos el abanico de tareas de nuestro agente. El proceso de entrenamiento incluirá algunos aspectos de la gestión de capital y del riesgo que forma parte integral de cualquier estrategia comercial.