
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 02): Primeros experimentos (II)
Introducción
En el artículo anterior, titulado "Desarrollo de un sistema de repetición - Simulación de mercado (Parte 01): Primeros Experimentos (I)", mostré las limitaciones de intentar construir un sistema de eventos con un tiempo de ejecución lo suficientemente corto como para generar una simulación de mercado adecuada. Quedó claro que no es posible conseguir menos de 10 milisegundos utilizando ese enfoque. En muchos casos, ese tiempo es bastante bajo. Sin embargo, si has examinado los archivos adjuntos al artículo, te habrás dado cuenta de que 10 milisegundos no es un tiempo suficientemente bajo. Entonces, ¿existe otro método que nos permita alcanzar un tiempo de 1 ó 2 milisegundos?
Antes de empezar a considerar cualquier cosa relacionada con el uso de tiempos en el rango de unos pocos milisegundos, es importante recordar a todo el mundo que no se trata de una tarea realmente sencilla. La razón principal es que el propio temporizador proporcionado por el sistema operativo no puede alcanzar estos valores. Por lo tanto, tenemos un gran, por no decir GIGANTE, problema entre manos. En este artículo, intentaré responder a esta pregunta y mostrar cómo podemos intentar resolverlo yendo más allá del límite de tiempo establecido por el sistema operativo. Sé que muchos pueden pensar que una CPU moderna es capaz de hacer miles de millones de cálculos por segundo. Sin embargo, una cosa es que la CPU realice cálculos y otra cosa es garantizar que todo lo que se ejecuta en el ordenador pueda manejar la tarea que necesitamos realizar. Es importante señalar que la propuesta aquí es utilizar MQL5 exclusivamente para esto, sin utilizar ningún código externo o DLLs, sólo MQL5 puro.
Planificación
Bien, para verificar esto, tendremos que hacer algunos cambios en la metodología. Si el proceso tiene éxito, no tendremos que volver a retocar el sistema de creación de ticks. Nos centraremos en otras cuestiones que nos ayuden a realizar un estudio o entrenamiento utilizando valores de tick reales o valores simulados. El ensamblaje de las barras de 1 minuto seguirá siendo el mismo. Ese será el objetivo principal de este artículo.
Así pues, adoptaremos un enfoque lo más genérico posible, y la mejor forma que encontré fue utilizar un modelo similar al cliente-servidor. Esta técnica ya la comenté en otro artículo titulado "Desarrollando un EA de trading desde cero (Parte 16): Acceso a los datos en la Web (II)". En él, presenté tres formas de transmitir información dentro de MetaTrader 5. Aquí, utilizaremos una de esas formas, concretamente un SERVICIO. Sí, la repetición de mercado será un servicio de MetaTrader 5.
Quizás estés pensando que voy a crear todo desde cero, pero yo me pregunto " ¡¿Por qué iba a hacer eso?!". El sistema ya está funcionando, sin embargo no está alcanzando el marco de tiempo deseado de 1 minuto. Y puede que me mires con una expresión que diga: " ¡¿Y crees que con cambiar a un servicio se solucionará?!?". En realidad, cambiar el sistema a un servicio no resolverá nuestro problema. Sin embargo, si aislamos, desde el principio, la creación de las barras de 1 minuto del resto del sistema EA, tendremos menos trabajo después, porque el propio EA provocará una ligera latencia en la ejecución de la construcción de la barra, y la razón de esto la explicaremos más adelante.
¿Ahora ves por qué usamos un servicio? Es más práctico que los otros métodos discutidos anteriormente. Podremos controlarlo tal y como expliqué en el artículo sobre el intercambio de mensajes entre un EA y un servicio, titulado "Desarrollando un EA comercial desde cero (Parte 17): Acceso a los datos en la web (III)". Sin embargo, aquí no nos centraremos en generar este control, sólo queremos que el servicio genere las barras que se colocarán en el gráfico. Pero para hacer las cosas más interesantes, vamos a utilizar la plataforma de una manera más creativa, no limitada sólo al uso de un EA y un servicio.
Sólo para recapitular, en el último intento de reducir el tiempo, obtuvimos el resultado que se muestra a continuación:
Esa fue la mejor marca. Aquí, vamos a batir ese tiempo desde el principio. Sin embargo, no quiero que te obsesiones totalmente con esos valores o con las pruebas que aquí se muestran. Esta secuencia de artículos sobre la creación de un sistema de repetición/simulador ya está en una fase mucho más avanzada, en la que he cambiado varias veces varios conceptos para que el sistema funcione realmente como se espera. A pesar de que, en este punto, lo que se presentará parece adecuado y alentador, cometí algunos errores o equivocaciones relacionadas con las pruebas de temporización. Estos errores no son simples de notar o entender en una etapa temprana del sistema. A medida que esta secuencia de artículos se desarrolla, te darás cuenta de que la cuestión de la sincronización es mucho más compleja e implica más cosas que simplemente conseguir que la CPU y la plataforma MetaTrader 5 arrojen una cierta cantidad de datos en el gráfico para una inmersión en el sistema de repetición/simulador.
Así que no tomes todo al pie de la letra de lo que se verá aquí. Sigue esta secuencia de artículos, ya que lo que se hará aquí está lejos de ser algo simple y fácil de lograr.
Implementación
Empecemos por crear las bases de nuestro sistema, que incluyen:
- Servicio para crear las barras de 1 minuto;
- Script para iniciar el servicio;
- EA para realizar las simulaciones (esto se desarrollará más adelante);
Definición del servicio de repetición de mercado
Para trabajar correctamente con el servicio, necesitamos actualizar nuestra clase C_Replay. Sin embargo, los cambios son tan menores que no entraré en detalles, pero básicamente se trata de código de retorno. Sin embargo, hay un punto que vale la pena destacar ya que hará algo extra, y el código se muestra a continuación.
#define macroGetMin(A) (int)((A - (A - ((A % 3600) - (A % 60)))) / 60) int Event_OnTime(void) { bool isNew; int mili; static datetime _dt = 0; if (m_ReplayCount >= m_ArrayCount) return -1; if (m_dt == 0) { m_Rate[0].close = m_Rate[0].open = m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last; m_Rate[0].tick_volume = 0; m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60; CustomRatesUpdate(def_SymbolReplay, m_Rate, 1); _dt = TimeLocal(); } isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt; m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt); mili = m_ArrayInfoTicks[m_ReplayCount].milisec; while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec) { m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last; m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open); m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high); m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low); m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol); isNew = false; m_ReplayCount++; } m_Rate[0].time = m_dt; CustomRatesUpdate(def_SymbolReplay, m_Rate, 1); mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili); if ((macroGetMin(m_dt) == 1) && (_dt > 0)) { Print("Tempo decorrido : ", TimeToString(TimeLocal() - _dt, TIME_SECONDS)); _dt = 0; } return (mili < 0 ? 0 : mili); }; #undef macroGetMin
Las partes resaltadas fueron agregadas al código original de la clase C_Replay. Lo que estamos haciendo es definir un tiempo de retardo, es decir, vamos a utilizar exactamente el valor informado por la línea, pero en milisegundos. Recuerda que este tiempo no será exacto, porque depende de algunas variables. Sin embargo, intentaremos que sea lo más cercano posible a 1 milisegundo.
Una vez hechos esos cambios, veamos el código del servicio, que está abajo en su totalidad:
#property service #property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Market Replay\C_Replay.mqh> //+------------------------------------------------------------------+ input string user01 = "WINZ21_202110220900_202110221759"; //Arquivo com ticks //+------------------------------------------------------------------+ C_Replay Replay; //+------------------------------------------------------------------+ void OnStart() { ulong t1; int delay = 3; if (!Replay.CreateSymbolReplay(user01)) return; Print("Aguardando permissão para iniciar replay ..."); GlobalVariableTemp(def_GlobalVariable01); while (!GlobalVariableCheck(def_SymbolReplay)) Sleep(750); Print("Serviço de replay iniciado ..."); t1 = GetTickCount64(); while (GlobalVariableCheck(def_SymbolReplay)) { if ((GetTickCount64() - t1) >= (uint)(delay)) { if ((delay = Replay.Event_OnTime()) < 0) break; t1 = GetTickCount64(); } } GlobalVariableDel(def_GlobalVariable01); Print("Serviço de replay finalizado ..."); } //+------------------------------------------------------------------+
El código anterior es el responsable de crear las barras. Colocando este código aquí, el sistema de repetición funcionará de forma independiente, estando poco influenciado por el funcionamiento de la plataforma MetaTrader 5. Esto nos permitirá trabajar en otras áreas relacionadas con el control, análisis y simulación de la repetición. Sin embargo, esto se hará más adelante.
Ahora una cosa interesante: observa en las zonas resaltadas que tenemos una función GetTickCount64. Nos proporcionará un sistema equivalente al visto en el artículo anterior, pero con una ventaja, aquí la resolución bajará a 1 milisegundo. La precisión no es exacta, es aproximada, pero el nivel de aproximación es muy cercano a lo que sería el movimiento real del mercado. Esto es independiente del hardware que se utilice. No obstante, se puede crear un bucle que garantice una mayor precisión, pero requerirá mucho trabajo, porque dependerá del tipo de hardware que se esté utilizando.
El siguiente paso es el script, que se muestra completo justo debajo:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Market Replay\C_Replay.mqh> //+------------------------------------------------------------------+ C_Replay Replay; //+------------------------------------------------------------------+ void OnStart() { Print("Aguardando Serviço de Replay ..."); while((!GlobalVariableCheck(def_GlobalVariable01)) && (!IsStopped())) Sleep(500); if (IsStopped()) return; Replay.ViewReplay(); GlobalVariableTemp(def_SymbolReplay); while ((!IsStopped()) && (GlobalVariableCheck(def_GlobalVariable01))) Sleep(500); GlobalVariableDel(def_SymbolReplay); Print("Script de Replay finalizado..."); Replay.CloseReplay(); } //+------------------------------------------------------------------+
Ambos códigos son bastante simples, como puede verse. Pero se comunican entre sí a través de variables globales mantenidas por la plataforma. De esta forma, tenemos el siguiente esquema:
Este esquema será mantenido por la propia plataforma. Si el script finaliza, el servicio se detendrá. Si el servicio se detiene, el símbolo que estamos utilizando para ejecutar la repetición dejará de recibir datos. Esto hace las cosas super sencillas y altamente sostenibles. Cualquier mejora, ya sea en la plataforma o en el hardware, se reflejará automáticamente en el rendimiento general. Esto es debido a las pequeñas latencias que se producen durante cada una de las operaciones realizadas por el proceso de servicio. Sólo afectará efectivamente al sistema en su conjunto, no tenemos que preocuparnos por el script o el EA que se desarrollen en el futuro. Cualquier mejora afectará únicamente al servicio.
Para ahorrarte la molestia de probar el sistema, puedes previsualizar el resultado en la imagen inferior. Así, tú, querido lector, no tienes que esperar un minuto entero para ver el resultado en tu gráfico.
Observa que el resultado está muy cerca del ideal. Los 9 segundos extra pueden eliminarse fácilmente con ajustes en el sistema. Lo ideal es que el tiempo sea inferior a 1 minuto, lo que facilita los ajustes, ya que sólo tenemos que añadir un retardo en el sistema. Es más fácil añadir latencia que reducirla. Pero si crees que no se puede reducir el tiempo del sistema, veámoslo más a fondo.
Si nos fijamos, hay un punto que genera un retraso en el sistema, y está en el servicio. El punto que realmente genera este retraso está resaltado en el siguiente extracto. Pero, ¿qué pasaría si convirtiéramos esa línea en un comentario? ¿Qué pasaría con el sistema?
t1 = GetTickCount64(); while (GlobalVariableCheck(def_SymbolReplay)) { // ... COMENTARIO ... if ((GetTickCount64() - t1) >= (uint)(delay)) { if ((delay = Replay.Event_OnTime()) < 0) break; t1 = GetTickCount64(); } } GlobalVariableDel(def_GlobalVariable01);
La línea resaltada ya no se ejecutará. En ese caso, te ahorraré probar el sistema localmente y tener que esperar un minuto más. Puedes ver el resultado de la ejecución en el siguiente vídeo. Puedes verlo entero o saltar a la parte en la que muestro sólo el resultado final. Siéntete libre de elegir.
En resumen, la gran dificultad es generar un retraso correctamente. Pero la pequeña variación en el tiempo de 1 minuto para crear la barra no es realmente un problema. Incluso en una cuenta real, no tendremos un tiempo exacto porque hay un pequeño retraso debido a la latencia en la transmisión de la información. Este retraso es bastante pequeño, pero aún así existe.
Velocidad máxima. ¿Es realmente así?
Aquí haremos un último intento para que el sistema funcione por debajo de 1 minuto.
Al observar los valores en milisegundos, te darás cuenta de que a veces hay una variación de sólo 1 milisegundo entre una línea y otra. Sin embargo, todos estos valores están dentro del mismo segundo. Por lo tanto, podemos hacer un pequeño cambio en el código. Le añadiremos un bucle, que puede suponer una gran diferencia en el conjunto del sistema.
El cambio se muestra a continuación:
#define macroGetMin(A) (int)((A - (A - ((A % 3600) - (A % 60)))) / 60) inline int Event_OnTime(void) { bool isNew; int mili; static datetime _dt = 0; if (m_ReplayCount >= m_ArrayCount) return -1; if (m_dt == 0) { m_Rate[0].close = m_Rate[0].open = m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last; m_Rate[0].tick_volume = 0; m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60; CustomRatesUpdate(def_SymbolReplay, m_Rate, 1); _dt = TimeLocal(); } isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt; m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt); mili = m_ArrayInfoTicks[m_ReplayCount].milisec; do { while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec) { m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last; m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open); m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high); m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low); m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol); isNew = false; m_ReplayCount++; } mili++; }while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec); m_Rate[0].time = m_dt; CustomRatesUpdate(def_SymbolReplay, m_Rate, 1); mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili); if ((macroGetMin(m_dt) == 1) && (_dt > 0)) { Print("Tempo decorrido : ", TimeToString(TimeLocal() - _dt, TIME_SECONDS)); _dt = 0; } return (mili < 0 ? 0 : mili); }; #undef macroGetMin
Observa que ahora tenemos un bucle externo que realiza esta prueba de 1 milisegundo. Dado que es muy difícil ajustar adecuadamente dentro del sistema para aprovechar ese único milisegundo, podría ser mejor eliminarlo.
Bueno, sólo hemos hecho este cambio, pero echa un vistazo al resultado en el vídeo a continuación.
Para los que quieran algo aún más rápido, comprueben el resultado:
Creo que es suficiente. Ahora tenemos la creación de la barra de 1 minuto por debajo de ese tiempo. Podemos hacer ajustes para lograr el tiempo perfecto mediante la adición de retrasos en el sistema. Sin embargo, no lo haré, porque la idea es tener un sistema que nos permita realizar estudios simulados. Cualquier tiempo cercano a 1 minuto está bien para que el operador se entrene y practique. No hace falta que sea algo exacto.
Conclusión
Ahora que tenemos el sistema básico para crear la repetición, podemos pasar a los siguientes puntos. Es importante señalar que todo se resolvió sólo con ajustes y el uso de las funciones presentes en el propio lenguaje MQL5, lo que demuestra que es capaz de hacer mucho más de lo que muchos creen que es posible.
Sin embargo, es importante señalar que el trabajo no ha hecho más que empezar. Aún queda mucho por hacer.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/10551





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Antes que nada quiero agradecerte por toda la información y el apoyo que nos brindas con tus publicaciones.
Estoy siguiendo este gran proyecto "Desarrollo de un sistema de repetición", pero me ha surgido un problema que no he podido solucionar, el cual expongo a continuación;
Al exportar los "Ticks" de MT5 para crear la base de datos e ingresarlos para el programa "C_Replay", me da el siguiente error "Foi gerados 0 posições de movimento" he intentado resolverlo pero no he tenido éxito. Quizás es un problema muy básico pero en verdad no he encontrado la manera de resolverlo. Me he percatado de que las columnas de <LAST><VOLUME> no tienen datos, pero así es como los proporciona MT5.
Gracias por tu atención.
Saludos Daniel.
Antes que nada quiero agradecerte por toda la información y el apoyo que nos brindas con tus publicaciones.
Estoy siguiendo este gran proyecto "Desarrollo de un sistema de repetición", pero me ha surgido un problema que no he podido solucionar, el cual expongo a continuación;
Al exportar los "Ticks" de MT5 para crear la base de datos e ingresarlos para el programa "C_Replay", me da el siguiente error "Foi gerados 0 posições de movimento" he intentado resolverlo pero no he tenido éxito. Quizás es un problema muy básico pero en verdad no he encontrado la manera de resolverlo. Me he percatado de que las columnas de <LAST><VOLUME> no tienen datos, pero así es como los proporciona MT5.
Gracias por tu atención.
Encontré una solución.Se debe adaptar la línea anterior con base en los datos proporcionados para el programa "C Replay".
Eu encontrei uma solução.A linha anterior deve ser adaptada com base nos dados fornecidos para o programa "C Replay".
Gracias por la sugerencia y por interesarte en el artículo. Pero sigue la secuencia y verás cuál fue la solución que encontré en el momento en que se escribieron estos artículos. Creo que comenzarás a ver el mercado de una manera diferente.