Desarrollo de un sistema de repetición — Simulación de mercado (Parte 03): Haciendo ajustes (I)
Introducción
En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 02): Primeros experimentos (II), construimos un sistema capaz de generar una barra de 1 minuto con un tiempo de procesamiento aceptable para su uso en simulaciones de mercado. Sin embargo, nos dimos cuenta de que no teníamos control sobre lo que estaba sucediendo. Nuestra habilidad se limitaba a seleccionar algunas cosas y ajustar otras. Una vez que el sistema estaba iniciado, teníamos pocas opciones disponibles.
En este artículo, vamos a mejorar esta situación. Vamos a activar algunos controles adicionales, para que podamos tener un estudio más controlado, por así decirlo. Añadiremos algunos controles extra para tener un estudio más controlado, por así decirlo. Aunque todavía tenemos mucho trabajo por delante para tener un sistema totalmente funcional en términos de análisis estadístico y control del gráfico, ya es un buen comienzo.
A pesar de todo el trabajo que aún nos queda por hacer, en este artículo haremos sólo algunos ajustes, así que será relativamente corto. No vamos a profundizar en detalles en este momento. Nuestro objetivo aquí es construir las bases de los controles necesarios para hacer la repetición más sencilla de realizar y analizar para aquellos que desean utilizar este sistema de hecho.
Planificación
Esta etapa de planificación es bastante sencilla, ya que, al observar cómo se dejó funcionando el sistema en el último artículo, es evidente lo que necesitamos hacer de inmediato. Es necesario crear una forma de control para pausar, dar play y, sobre todo, seleccionar un momento específico para iniciar el estudio.
Tal como está actualmente, siempre empezaremos desde el primer ticket de negociación. Supongamos que queremos hacer un estudio a partir de la quinta hora de mercado, es decir, a partir de las 14:00 horas (considerando que el mercado abre a las 9:00). En este caso, tendríamos que esperar 5 horas de repetición para, solo entonces, realizar el análisis deseado. Esto es totalmente inviable, ya que si intentamos parar la repetición, se cerrará y tendremos que empezar de nuevo desde el primer ticket de negociación.
Ahora está claro lo que necesitamos hacer de inmediato, ya que la forma en que está actualmente es desmotivante e impide su uso, aunque sea interesante.
Ya tenemos una dirección a seguir, por lo que podemos avanzar a la fase de implementación.
Implementación
La implementación será bastante interesante, ya que tendremos que seguir diversos caminos, desde los más sencillos hasta los más variados, para realmente conseguir implementar el sistema de control. Sin embargo, todos los pasos son fáciles de entender, siempre y cuando tú prestes atención a las explicaciones y sigas los artículos en orden, sin saltarte ninguno de ellos o intentar adelantar etapas en el desarrollo.
Contrariamente a lo que muchos puedan pensar, no vamos a utilizar DLLs en el sistema. Utilizaremos pura y simplemente el lenguaje MQL5 para implementar el sistema de repetición. La idea es aprovechar al máximo lo que MetaTrader 5 nos ofrece, mostrando hasta dónde podemos llegar dentro de la plataforma para crear las funcionalidades necesarias. Recurrir a una implementación externa quita parte de la diversión de trabajar con MQL5, dando la impresión de que no puede satisfacer nuestras necesidades.
Si observas el código utilizado en el artículo anterior, verás que estábamos usando un sistema que hacía uso de un servicio para crear la repetición, además de un script que lo iniciaba. Este script permitía que el servicio enviara los ticks al símbolo personalizado, creando así la repetición. Básicamente, usábamos un mecanismo simple de conmutación. Sin embargo, para tener un control más efectivo, este método no es adecuado. Necesitamos seguir un camino un poco más complejo.
Cómo crear un EA ultra básico.
Inicialmente, intentaremos utilizar un camino en el que el control se llevará a cabo mediante un EA. Este EA será responsable de controlar cuándo el servicio debe o no generar los tickets para las barras.
Pero, ¿por qué un EA? Bueno, podríamos usar un indicador en lugar del EA, que también funcionaría de la misma manera. Sin embargo, quiero usar el EA porque más adelante lo necesitaremos para crear la simulación de órdenes. Además, intentaremos utilizar el mismo sistema de órdenes que se utilizó en otra serie de artículos de mi autoría, titulada "Desarrollando un EA comercial desde cero". Pero, por ahora, no te preocupes por el sistema de órdenes, todavía tenemos mucho trabajo que hacer antes de llegar allí.
El código completo de nuestro EA básico se puede ver a continuación:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Market Replay\C_Controls.mqh> //+------------------------------------------------------------------+ C_Controls Control; //+------------------------------------------------------------------+ int OnInit() { Control.Init(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) {} //+------------------------------------------------------------------+ void OnTick() {} //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { Control.DispatchMessage(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+
Observa que el código es lo más simple posible, pero es suficiente para permitirnos controlar el funcionamiento del servicio. Ahora, vamos a echar un vistazo a una parte específica del código, que es la clase de objetos de control destacada arriba. En esta etapa inicial de desarrollo, este código no es muy complicado. Estamos implementando solo un botón que permite dar play o pausar el servicio de repetición. Vamos a examinar esta clase en detalle.
Lo primero que hay que tener en cuenta es el código que se muestra a continuación:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include <Market Replay\Interprocess.mqh> //+------------------------------------------------------------------+ #define def_ButtonPlay "Images\\Market Replay\\Play.bmp" #define def_ButtonPause "Images\\Market Replay\\Pause.bmp" #resource "\\" + def_ButtonPlay #resource "\\" + def_ButtonPause //+------------------------------------------------------------------+ #define def_PrefixObjectName "Market Replay _ "
La primera cosa a notar es el archivo de encabezado destacado en AZUL, que mostraré con más calma posteriormente. Luego, tenemos algunas definiciones de los objetos bitmap que representarán los botones de play y pause. Nada muy complicado. Después de estos puntos, entramos en el código de la clase, que es bastante compacto. Puedes ver todo el código completo a continuación.
class C_Controls { private : //+------------------------------------------------------------------+ string m_szBtnPlay; long m_id; //+------------------------------------------------------------------+ void CreateBtnPlayPause(long id) { m_szBtnPlay = def_PrefixObjectName + "Play"; ObjectCreate(id, m_szBtnPlay, OBJ_BITMAP_LABEL, 0, 0, 0); ObjectSetInteger(id, m_szBtnPlay, OBJPROP_XDISTANCE, 5); ObjectSetInteger(id, m_szBtnPlay, OBJPROP_YDISTANCE, 25); ObjectSetInteger(id, m_szBtnPlay, OBJPROP_STATE, false); ObjectSetString(id, m_szBtnPlay, OBJPROP_BMPFILE, 0, "::" + def_ButtonPause); ObjectSetString(id, m_szBtnPlay, OBJPROP_BMPFILE, 1, "::" + def_ButtonPlay); } //+------------------------------------------------------------------+ public : //+------------------------------------------------------------------+ C_Controls() { m_szBtnPlay = NULL; } //+------------------------------------------------------------------+ ~C_Controls() { ObjectDelete(ChartID(), m_szBtnPlay); } //+------------------------------------------------------------------+ void Init(void) { if (m_szBtnPlay != NULL) return; CreateBtnPlayPause(m_id = ChartID()); GlobalVariableTemp(def_GlobalVariableReplay); ChartRedraw(); } //+------------------------------------------------------------------+ void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) { u_Interprocess Info; switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == m_szBtnPlay) { Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE); GlobalVariableSet(def_GlobalVariableReplay, Info.Value); } break; } } //+------------------------------------------------------------------+ };
Existen dos funciones principales aquí: Init y DispatchMessage. Estas dos funciones realizan todo el trabajo necesario para que el EA funcione en esta etapa inicial. Para explicar mejor algunos de estos detalles, vamos a observar los fragmentos de estas dos funciones a continuación, empezando con la función Init.
void Init(void) { if (m_szBtnPlay != NULL) return; CreateBtnPlayPause(m_id = ChartID()); GlobalVariableTemp(def_GlobalVariableReplay); ChartRedraw(); }
Cuando se llama a la función Init, primero verifica si los controles ya han sido creados. Si esto ya ha ocurrido, la función retorna. Esto es importante porque, al cambiar el período del gráfico o hacer algún cambio que requiera que el EA se recargue en el gráfico (lo cual es bastante común), el estado del servicio de repetición no se modificará. Es decir, si el servicio está en ejecución, continuará en pausa; si está en reproducción, seguirá enviando ticks.
Si es la primera llamada, se crean los controles básicos, que por el momento son solo los botones de play y pause. Luego, creamos una variable global del terminal, que se usará para la comunicación entre los procesos del EA y del servicio. En este momento, solo creamos la variable, sin asignar ningún valor a ella.
Después de eso, necesitamos aplicar los objetos en la pantalla. Esto es importante, ya que si esta actualización no se realiza, el EA se cargará, pero el servicio se quedará parado, llevándote a pensar que el sistema se ha bloqueado. En realidad, estamos esperando que MetaTrader 5 actualice el gráfico para nosotros, de modo que los objetos se tracen y podamos reproducir la repetición de mercado.
¿Notaste lo sencillo que es? Ahora vamos a analizar el código de la función DispatchMessage, que también es sencillo en esta etapa actual y se puede ver a continuación:
void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) { u_Interprocess Info; switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == m_szBtnPlay) { Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE); GlobalVariableSet(def_GlobalVariableReplay, Info.Value); } break; } }
Estamos usando MetaTrader 5 para controlar las cosas por nosotros. Utilizamos una unión llamada u_Interprocess, para ajustar la variable global del terminal mediante la verificación del estado del botón bitmap. De esta manera, ajustamos la variable global del terminal para transmitir la información al proceso del servicio, que es responsable de crear la repetición.
Por lo tanto, siempre iniciaremos el sistema de repetición en pausa. Una vez que el EA, con sus objetos, esté cargado en el gráfico, podremos dar play o pausar la repetición de mercado como deseemos. Esto hace las cosas un poco más interesantes.
Cómo entender el archivo Interprocess.mqh
Como puedes haber imaginado, al cambiar el sistema de usar un script para usar un EA, también hubo algunos cambios en el servicio de repetición. Antes de abordar esos cambios, vamos a echar un vistazo al archivo Interprocess.mqh, que se puede ver en su totalidad en su etapa actual de desarrollo en el código a continuación:#define def_GlobalVariableReplay "Replay Infos" //+------------------------------------------------------------------+ union u_Interprocess { double Value; struct st_0 { bool isPlay; struct st_1 { char Hours; char Minutes; }Time[3]; }s_Infos; };
Esta simple definición aquí nos da un nombre, pero no es cualquier nombre. Este será el nombre de la variable global del terminal, que se utilizará en esta etapa para permitir la comunicación entre el EA y el Servicio. La parte que puede ser complicada para aquellos con menos experiencia en programación es la unión.
Vamos a entender qué representa realmente esta unión para entender luego cómo se usa para transmitir información entre el EA y el Servicio. Para empezar a entender toda esta complejidad, necesitas saber cuántos bits representa cada tipo de dato cuando se usa. Para facilitarte la vida, observa la tabla a continuación:
Tipo | Número de bits |
---|---|
Bool | 1 bit |
Char o UChar | 8 bits |
Short o UShort | 16 bits |
Int o UInt | 32 bits |
Long o ULong | 64 bits |
En esta tabla, tenemos los tipos enteros con y sin signo, seguidos por el número de bits (no confundas bits con bytes). Un bit es la unidad más pequeña de información, que representa un estado encendido o apagado, o 1 y 0 en el sistema binario. Un byte representa un conjunto de bits.
Al observar esta tabla, quizás no quede claro para algunos la siguiente idea: en una variable de tipo uchar, tendremos 8 variables de tipo bool. Es decir, una variable uchar corresponde a una "unión" (aunque la palabra no sea la más adecuada) de 8 variables bool. En un código, quedaría así:
union u_00 { char info; bool bits[8]; }Data;
La extensión de esta unión es de 8 bits, o 1 byte. Puedes modificar el contenido de info escribiendo en el array bits y seleccionando una posición específica. Por ejemplo, para que Data.info sea igual a 0x12, puedes hacer una de las dos opciones que se muestran a continuación:
Data.info = 0x12; ou Data.bits[4] = true; Data.bits[1] = true;
De una manera u otra, obtendrás el mismo resultado, siempre que la variable Data.info tenga todos sus bits iniciales en 0. Eso es lo que representa una unión.
Ahora, volviendo a nuestro código original. El tipo más grande encontrado en un sistema de 64 bits es el tipo long (con signo) o ulong (sin signo). La diferenciación entre signo y sin signo ocurre porque, en el caso del signo, es posible representar valores negativos, mientras que en el caso sin signo, solo se pueden representar valores positivos. Entonces, en este caso, tendríamos algo similar:
Cada uno de los cuadrados representa 1 bit, y el nombre "QWORD" proviene del Assembly, que es la lengua madre de todos los lenguajes de programación modernos. Esta misma estructura se encuentra en otro tipo, que son los tipos flotantes.
Las variables flotantes son variables cuyo valor no es exacto, pero aún así pueden ser usadas para representar valores computables. Básicamente, existen 2 tipos:
Tipo | Número de bits |
---|---|
Float | 32 bits |
Double | 64 bits |
De la misma manera que los tipos enteros vistos anteriormente, donde cada bit representa un estado encendido o apagado. Aquí, en los tipos flotantes, no tenemos la misma lógica para representar un valor. Éstos siguen un principio un poco diferente para ser creados, pero eso no es relevante en este momento. El detalle importante aquí es otro.
Al observar el tipo utilizado en las variables globales del terminal, vemos que solo pueden ser del tipo flotante, más precisamente del tipo double, es decir, 64 bits. Ahora, surge la pregunta: ¿cuál es el tipo entero que tiene la misma longitud? Exactamente lo que tú respondiste, el tipo long, que también posee 64 bits. Por lo tanto, al unir un tipo long y un double, podemos representar dos cosas completamente diferentes al mismo tiempo.
Pero hay una cuestión un poco complicada aquí. ¿Cómo sabrás cuál es el tipo utilizado? Para resolver esto, no utilizamos un tipo completo, sino que utilizamos sus fragmentos y les damos nombres a esos fragmentos. De esta manera, nace la unión, que se ve en el código del archivo Interprocess.mqh.
De hecho, no vamos a usar el tipo double. No es para nada adecuado ni sencillo intentar escribir directamente a mano el valor que debe ser creado en el tipo double. En su lugar, utilizamos las partes nombradas para realizar esa creación, y los bits adecuados se llenan con los valores correctos, representando 0s o 1s. Luego, ponemos el valor double en la variable global del terminal, y el otro proceso, que en este caso es el servicio, obtendrá el valor y lo decodificará, sabiendo exactamente qué debe hacer.
Observa que esto se hace siguiendo reglas muy simples y fáciles de entender. Sería muy difícil hacer esto si intentáramos crear directamente valores flotantes y luego entender el significado de esos valores.
De esta manera, creo que has comprendido qué es una unión y cómo la utilizaremos de hecho. Pero recuerda: en caso de querer usar variables globales del terminal, cuyo tipo es double y posee 64 bits, la unión creada no puede exceder esos mismos 64 bits, de lo contrario, se perderá alguna información.
Cómo se crea el servicio de repetición
Esta tal vez sea la parte en la que necesitas prestar mucha atención para entender lo que está sucediendo. Si dejas pasar algo sin entenderlo, podrías confundirte. A pesar de parecer simple, hay detalles que, si no se comprenden bien, pueden hacer que te cuestiones por qué el sistema funciona como se describe y se demuestra, pero tú no puedes hacerlo funcionar en tu propia estación de trabajo.
Vamos a ver el archivo del servicio de repetición. Actualmente, aún es bastante compacto y simple. Puedes verlo en su totalidad en el código a continuación:
#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; long id; u_Interprocess Info; bool bTest = false; if (!Replay.CreateSymbolReplay(user01)) return; id = Replay.ViewReplay(); Print("Aguardando permissão para iniciar replay ..."); while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750); Print("Serviço de replay iniciado ..."); t1 = GetTickCount64(); while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value))) { if (!Info.s_Infos.isPlay) { if (!bTest) bTest = (Replay.Event_OnTime() > 0); else t1 = GetTickCount64(); }else if ((GetTickCount64() - t1) >= (uint)(delay)) { if ((delay = Replay.Event_OnTime()) < 0) break; t1 = GetTickCount64(); } } Replay.CloseReplay(); Print("Serviço de replay finalizado ..."); } //+------------------------------------------------------------------+
Si tomas este código, y simplemente creas el archivo "WINZ21_202110220900_202110221759", que se utiliza como base para crear la repetición, e intentas ejecutarlo, verás que no pasará nada, e incluso si utilizas el archivo que está en el adjunto, e intentas ejecutarlo desde este código, tampoco pasará nada. ¿Pero por qué? La razón es el código id = Replay.ViewReplay(); este código hace algo, que necesitas entender, para usar realmente el sistema de repetición de mercado. No importa lo que hagas, si no entiendes lo que está pasando, nada tendrá sentido. Pero antes de ver el código dentro de ViewReplay(), entendamos primero el flujo de datos del código anterior.
Para entender cómo funciona en la práctica, vamos a dividirlo en partes más pequeñas, comenzando con el siguiente fragmento:
if (!Replay.CreateSymbolReplay(user01)) return;
Esta línea cargará los datos de ticks negociados presentes en el archivo indicado. Si esta carga no tiene éxito, el servicio simplemente se cerrará.
id = Replay.ViewReplay();
Esta línea cargará el EA, pero esto se verá con más detalle más adelante. Entonces vamos a continuar.
while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
La línea anterior estará dentro de un bucle, esperando que el EA sea cargado o que algo más cree la variable global del terminal. Esta variable servirá como forma de comunicación entre los procesos que se están ejecutando fuera del entorno del servicio.
t1 = GetTickCount64();
Esta línea realiza la primera captura para el contador interno del servicio. Esta primera captura puede ser necesaria o no. Normalmente, es completamente innecesaria, ya que el sistema entra inmediatamente en modo pausa al ser habilitado.
while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)))
Este punto es bastante interesante. Tenemos dos pruebas aquí. Si una de ellas falla, el servicio de repetición se cerrará. En el primer caso, verificamos si la ventana del activo de repetición está presente o no en el terminal. Si el operador cierra esta ventana, el servicio de repetición se finalizará, ya que el operador ya no estará ejecutando la repetición. En el segundo caso, probamos y, al mismo tiempo, capturamos el valor presente en la variable global del terminal. Si esta variable deja de existir, el servicio también se cerrará.
u_Interprocess Info; //... if (!Info.s_Infos.isPlay)
Aquí hacemos una verificación de la condición informada por el operador o usuario de la repetición. Si estamos en el modo play, esta prueba fallará. Pero si estamos en el modo pause, tendrá éxito. Observa cómo utilizamos la unión para capturar el bit correcto dentro del valor double. Sin esta unión, esto sería algo impensable de hacer.
Una vez que estamos en modo pausa, ejecutamos la siguiente línea:
if (!bTest) bTest = (Replay.Event_OnTime() > 0); else t1 = GetTickCount64();
Esta línea permitirá que solo el primer tick de negociación sea enviado al activo. Esto es importante por algunas razones que se tratarán en otra oportunidad. Una vez que esto se haya hecho, en cualquier otro momento en que el servicio de repetición esté "en pausa", estaremos capturando el valor actual del cronómetro. Es cierto que este modo "en pausa" no se refiere al hecho de que el servicio esté realmente en pausa. Simplemente no está enviando ticks al activo de repetición, por eso digo que está "en pausa".
Pero si el usuario u operador desea comenzar o reanudar la ejecución de la repetición de mercado, entramos en una nueva línea de código. Se ve justo debajo:
else if ((GetTickCount64() - t1) >= (uint)(delay))
Esta línea probará si debemos o no enviar un nuevo tick basado en el valor de retraso (delay) necesario entre un tick y otro. Este valor se obtiene en la siguiente línea del código.
if ((delay = Replay.Event_OnTime()) < 0) break;
Observa que, si el retraso es menor que 0, el servicio de repetición se cerrará. Esto normalmente ocurre cuando el último tick negociado fue enviado al activo de repetición.
Estas funciones anteriores continuarán ejecutándose hasta que se envíe el último tick o hasta que se cierre el gráfico del activo de repetición. Cuando eso suceda, se ejecutará la siguiente línea:
Replay.CloseReplay();
Esta línea terminará definitivamente la repetición.
Todo este código es bastante claro y fácil de entender. Pero tal vez hayas notado que hay varios puntos aquí que se refieren a una clase, la C_Replay. Vamos a echar un vistazo a esta clase, y aunque su código tiene muchas similitudes con lo encontrado en los artículos anteriores, hay una parte que merece ser destacada. Es precisamente eso lo que vamos a explorar ahora en el próximo tema.
ViewReplay de la clase C_Replay. ¿Por qué es tan importante?
Este código se puede ver a continuación:
long ViewReplay(void) { m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); ChartRedraw(m_IdReplay); return m_IdReplay; }
Podrías pensar: ¡¿Qué tienen de tan importante estas 4 líneas como para permitir o impedir la creación de la repetición?! A pesar de ser un código bastante sencillo, es extremadamente poderoso. Poderoso hasta el punto de evitar que las cosas funcionen, incluso si todo parece estar correcto.
Así que vamos a entenderlo. Lo primero que hacemos es abrir un gráfico con el nombre del activo de repetición y establecemos el período de tiempo en 1 minuto. Como se vio en los dos artículos anteriores, podemos cambiar este tiempo en cualquier momento que queramos.
Después de eso, cargamos un template específico y lo aplicamos a la ventana del gráfico recién abierta. Es importante observar que este template es bastante específico, no es cualquiera. Para crear este template, en caso de que lo hayas eliminado (estará en el anexo), debes compilar el EA del sistema de repetición de mercado y aplicarlo a cualquier activo. Luego, guarda ese gráfico como un template, con el nombre "Market Replay", solo eso. Si este archivo no existe o el EA no está presente en él, todo el sistema fallará, no importa lo que hayas hecho.
De alguna manera, esto podría resolverse si, en lugar del EA, se usara un indicador. En ese caso, llamaríamos a este indicador vía MQL5 (en teoría). Pero como mencioné al principio de este artículo, tengo mis razones para usar un EA en lugar del indicador. Por lo tanto, para resolver el problema de carga de la manera más simple posible, usamos un template que contiene el EA del sistema de repetición.
Sin embargo, el simple hecho de hacer esto no garantiza mucho. Cuando se carga el EA, este crea la variable global del terminal, informando al servicio que el sistema está listo para funcionar. Sin embargo, los controles tardarán un poco en aparecer. Para agilizar un poco las cosas, usamos una llamada para forzar la actualización de los objetos en el gráfico del activo de repetición.
Ahora devolvemos el ID del gráfico del activo de repetición, ya que no tendremos otra forma de hacer esto en otro lugar, y necesitamos esta información para que el servicio sepa cuándo se ha cerrado el gráfico.
Todas las demás funciones de la clase C_Replay son bastante simples de entender y no merecen más atención aquí en este artículo.
Conclusión
En el video de abajo, puedes ver cómo se carga el sistema y cómo funciona en la práctica.
En el próximo artículo, crearemos el sistema de control de posición para que podamos elegir en qué momento se iniciará la repetición. ¡Nos vemos allí!
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/10706
- 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