Português
preview
Desarrollo de un sistema de repetición (Parte 58): Volvemos a trabajar en el servicio

Desarrollo de un sistema de repetición (Parte 58): Volvemos a trabajar en el servicio

MetaTrader 5Ejemplos | 30 julio 2024, 10:01
15 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Desarrollo de un sistema de repetición (Parte 57): Diseccionamos el servicio de prueba, expliqué en detalle el código fuente utilizado para demostrar una posible forma de comunicación entre los módulos que usaremos en nuestro sistema de repetición/simulador.

A pesar de que ese código nos da una idea de lo que realmente necesitaremos implementar, aún le falta un detalle importante para ser verdaderamente útil para nuestro sistema: la posibilidad de hacer uso de plantillas. Tal vez pienses que no es realmente importante si no operas realmente o no comprendes bien el poder que las plantillas nos brindan tanto en términos de codificación como de configuración de MetaTrader 5.

Sin embargo, saber, entender y comprender las plantillas reduce considerablemente nuestra carga de trabajo. Hay cosas que son muy simples de hacer con plantillas, pero que se vuelven extremadamente complejas y difíciles de implementar cuando intentamos programarlas directamente. Quizás en el futuro explique cómo hacer algunas cosas usando solo plantillas. Por ahora, tenemos otras tareas más urgentes.

Sinceramente, creía haber logrado que no fuera necesario modificar los módulos de control y de mouse. Sin embargo, debido a algunos detalles que veremos en los próximos artículos, será necesario realizar algunos pequeños cambios en ambos módulos. Esto lo veremos más adelante; en este artículo, comprenderemos cómo convertir el conocimiento adquirido en el artículo anterior en algo plausible y funcional. Para separar las cosas, pasemos a un nuevo tema.


Modificación del antiguo servicio de repetición/simulación

A pesar de que ha pasado bastante tiempo sin hacer modificaciones o mejoras en el código del repetidor/simulador, algunos encabezados involucrados en la construcción del ejecutable del repetidor/simulador han sufrido modificaciones y cambios. Quizás el más evidente sea la eliminación del archivo de encabezado InterProcess.mqh. Este fue sustituido por un archivo con un propósito mucho más amplio, el archivo de encabezado Defines.mqh.

El hecho de que ya hayamos realizado algunos cambios en los módulos de control y de mouse para usar este nuevo archivo de encabezado, nos obliga a hacer lo mismo aquí en el servicio de repetición/simulador. Por esta razón, al intentar compilar el servicio de repetición/simulador usando la nueva composición de archivos de encabezado, obtendremos como resultado el mensaje de error de compilación. Dichos fallos pueden verse en la figura 01.

Fig 01

Figura 01 - Intento de compilar el servicio de Repetición/Simulador

De todos los errores que puedan aparecer, primero debes resolver estos dos, que están destacados. Para hacer esto, será necesario abrir el archivo de encabezado C_Simulation.mqh y modificar el código, como se muestra en el siguiente fragmento. Ten en cuenta que todo lo que ha sido necesario hacer fue eliminar la línea 4 y reemplazarla por el contenido de la línea 5. De esta forma, el archivo C_Simulation.mqh quedará adecuado al nuevo modelo que estamos usando.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\..\Auxiliar\Interprocess.mqh"
05. #include "..\..\Defines.mqh"
06. //+------------------------------------------------------------------+
07. #define def_MaxSizeArray    16777216 // 16 Mbytes de posições
08. //+------------------------------------------------------------------+
09. class C_Simulation
10. {
11.    private   :
12. //+------------------------------------------------------------------+
13.       int       m_NDigits;
14.       bool       m_IsPriceBID;

Fragmento del código fuente del archivo C_Simulation.mqh

Al igual que se hizo en el archivo de encabezado C_Simulation.mqh, tendremos que hacer algo similar en el archivo C_FilesBars.mqh. Para ello, abre el archivo de encabezado C_FilesBars.mqh y modifica el código como se muestra en el siguiente fragmento.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\..\Auxiliar\Interprocess.mqh"
05. #include "..\..\Defines.mqh"
06. //+------------------------------------------------------------------+
07. #define def_BarsDiary   1440
08. //+------------------------------------------------------------------+
09. class C_FileBars
10. {
11.    private   :
12.       int      m_file;

Fragmento del código fuente del archivo C_FilesBars.mqh

Observa que, en ambos fragmentos, hemos eliminado el archivo de encabezado InterProcess.mqh y hemos utilizado en su lugar el archivo de encabezado Defines.mqh. Una vez realizadas estas dos modificaciones, gran parte del código ya se esperará correctamente por el servicio de repetición/simulador. Sin embargo, existe un problema. Si comparas el contenido de los archivos de encabezado InterProcess.mqh y Defines.mqh, verás que Defines.mqh no menciona las variables globales del terminal. Sin embargo, el sistema de repetición/simulación aún hace referencia a tales variables.

Para ser más precisos, tales variables se utilizan precisamente en el código del archivo C_Replay.mqh. Pero ese no es nuestro principal problema aquí. Tenemos otro problema. Tal vez en el futuro decida organizar el código de otra manera para separar aún más las cosas y así lograr mayor estabilidad y flexibilidad en todo el sistema. Pero, por ahora, solo adaptaré lo que ya se está haciendo. No quiero tener que hacer cambios radicales en todo el sistema solo para obtener una pequeña mejora en términos de flexibilidad y estabilidad, aunque siempre es bueno mejorar ambas.

Para no perdernos, separemos la explicación en temas. Lo primero que haremos será corregir un error que, aunque no es muy grave, perturba una de las premisas de la programación orientada a objetos: el encapsulamiento.


Revisar el encapsulamiento del código.

Uno de los problemas más serios que podemos crear en cualquier código es no respetar algunos principios que hacen de la programación orientada a objetos algo tan seguro. Durante mucho tiempo, he descuidado y utilizado de forma incorrecta una parte muy específica del código para tener un acceso fácil a ciertos datos con el fin de promover la repetición/simulación.

A partir de ahora, no se utilizará más. Me refiero a la ruptura del encapsulamiento presente en el código de la clase C_ConfigService.

Si observas el código presente en el archivo de encabezado de esta clase, C_ConfigService.mqh, notarás que existe una cláusula protected declarada y, dentro de esta cláusula, hay variables. Es precisamente la existencia de estas variables aquí lo que rompe el encapsulamiento. Aunque estas variables solo se usen en las clases C_ConfigService y C_Replay, no es adecuado permitir que sean visibles fuera de la clase C_ConfigService. En el estado actual, estas variables son visibles. Si observas el código de la clase C_Replay, verás que estas variables son modificadas por esa clase, y precisamente esto no es adecuado. En C++, existen formas de hacer que las variables privadas de las clases sean visibles y modificables desde fuera de la clase base. Sin embargo, esta práctica a menudo complica mucho el código y resulta difícil de mantener, por no mencionar lo complicado que resulta mejorarlo.

Dado que MQL5 deriva de C++ y trata de evitar prácticas potencialmente peligrosas que pueden hacerse en C++, creo que es más adecuado mostrar y utilizar correctamente las tres premisas básicas de la programación orientada a objetos.

Al realizar esta modificación en el código del archivo de encabezado C_ConfigService.mqh, lograremos que el encapsulamiento vuelva a ser utilizado en nuestro sistema. Pero esto nos obligará a hacer algunos otros cambios en las capas superiores. Es decir, el código de la clase C_Replay, que se encuentra en el archivo de encabezado C_Replay.mqh, sufrirá cambios drásticos. Además, mejoraremos un poco más nuestro código para que el código fuente del servicio de repetición/simulador esté menos anidado. De esta forma, intentaremos hacer que las cosas sucedan en pasos más pequeños, lo que facilitará el mantenimiento. Al hacer pasos más cortos, podemos controlar mejor lo que está ocurriendo. Esto será muy beneficioso para los próximos artículos, ya que necesitaremos implementar algo más complicado, que involucra una serie de desarrollos.

Vamos a ver lo que vamos a hacer para que las cosas sean más adecuadas. Para ello, será necesario abrir el archivo de encabezado C_ConfigService.mqh y modificar el código como se muestra en el siguiente fragmento. Muestro el fragmento porque el resto del código se mantendrá igual que antes. Pero los cambios mostrados en el fragmento ya nos garantizan que lograremos el encapsulamiento.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Support\C_FileBars.mqh"
05. #include "Support\C_FileTicks.mqh"
06. #include "Support\C_Array.mqh"
07. //+------------------------------------------------------------------+
08. class C_ConfigService : protected C_FileTicks
09. {
10.    protected:
11.         datetime m_dtPrevLoading;
12.         int      m_ReplayCount,
13.                  m_ModelLoading;
14. //+------------------------------------------------------------------+
15. inline void FirstBarNULL(void)
16.          {
17.             MqlRates rate[1];
18.             int c0 = 0;
19.             
20.             for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
21.             rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
22.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
23.             rate[0].tick_volume = 0;
24.             rate[0].real_volume = 0;
25.             rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
26.             CustomRatesUpdate(def_SymbolReplay, rate);
27.             m_ReplayCount = 0;
28.          }
29. //+------------------------------------------------------------------+
30.    private   :
31.       enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
32.       enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
33.       struct st001
34.       {
35.          C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
36.          int      Line;
37.       }m_GlPrivate;
38.       string    m_szPath;
39.       bool      m_AccountHedging;
40.       datetime  m_dtPrevLoading;
41.       int       m_ReplayCount,
42.                 m_ModelLoading;
43. //+------------------------------------------------------------------+
44. inline void FirstBarNULL(void)
45.          {
46.             MqlRates rate[1];
47.             int c0 = 0;
48.             
49.             for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
50.             rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
51.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
52.             rate[0].tick_volume = 0;
53.             rate[0].real_volume = 0;
54.             rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
55.             CustomRatesUpdate(def_SymbolReplay, rate);
56.             m_ReplayCount = 0;
57.          }
58. //+------------------------------------------------------------------+
59. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)

Fragmento del código fuente del archivo C_ConfigService.mqh

Ten en cuenta que el contenido presente entre las líneas 11 y 13 se ha trasladado a las líneas 40 y 42. Es decir, ahora no será posible acceder a estas variables fuera del cuerpo de la clase C_ConfigService. Además de esto, también se realizó otro cambio. Este cambio podría haberse ignorado, pero como algunas cosas no se van a usar fuera de la clase, decidí hacer que el procedimiento FirstBarNULL sea privado. Así, el contenido presente entre las líneas 15 y 28 se transfirió, quedando ahora entre las líneas 44 y 57.

Es claro que, al hacer estas modificaciones en el archivo real, el número de líneas será diferente. Ya que el código eliminado dejará de formar parte del código de la clase. Sin embargo, decidí dejarlo así en el fragmento para que puedas notar claramente qué fue cambiado. De esta manera, creo que queda más claro y sencillo entender lo que fue modificado.

Muy bien. Al realizar estos cambios, ahora necesitaremos modificar radicalmente el código presente en el archivo C_Replay.mqh. Pero para separar adecuadamente las cosas, dejemos esto para el próximo tema.


Reiniciamos la codificación de la clase C_Replay

Aunque el nombre de este tema puede parecer un poco desalentador. Ya que denota que intentaremos reinventar la rueda, que ya había sido construida. No quiero que te sientas desmotivado al ver este título. Al contrario. Quiero que comprendas que, de hecho, tenemos que rehacer gran parte del código referente a la clase C_Replay. Pero todo lo que hemos aprendido y visto a lo largo de esta serie de artículos no se perderá. Lo que haremos será adaptarnos a un nuevo escenario o una nueva forma de hacer las cosas, ya que hay cosas que no se podrán hacer como se hacían anteriormente.

A continuación, se muestra todo el código presente y perteneciente a la clase C_Replay de este artículo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. //+------------------------------------------------------------------+
006. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
007. #resource "\\" + def_IndicatorControl
008. //+------------------------------------------------------------------+
009. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""))
010. //+------------------------------------------------------------------+
011. #define def_ShortNameIndControl "Market Replay Control"
012. //+------------------------------------------------------------------+
013. class C_Replay : public C_ConfigService
014. {
015.    private   :
016.       long      m_IdReplay;
017.       struct st00
018.       {
019.          ushort Position;
020.          short  Mode;
021.       }m_IndControl;
022. //+------------------------------------------------------------------+
023. inline bool MsgError(string sz0) { Print(sz0); return false; }
024. //+------------------------------------------------------------------+
025. inline void UpdateIndicatorControl(void)
026.          {
027.             uCast_Double info;
028.             int handle;
029.             double Buff[];
030.             
031.             if ((handle = ChartIndicatorGet(m_IdReplay, 0, def_ShortNameIndControl)) == INVALID_HANDLE) return;
032.             info.dValue = 0;
033.             if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
034.                info.dValue = Buff[0];
035.             IndicatorRelease(handle);
036.             if ((short)(info._16b[0]) != SHORT_MIN)
037.                m_IndControl.Mode = (short)info._16b[1];
038.             if (info._16b[0] != m_IndControl.Position)
039.             {
040.                if (((short)(info._16b[0]) != SHORT_MIN) && ((short)(info._16b[1]) == SHORT_MAX))
041.                   m_IndControl.Position = info._16b[0];
042.                info._16b[0] = m_IndControl.Position;
043.                info._16b[1] = (ushort)m_IndControl.Mode;
044.                EventChartCustom(m_IdReplay, evCtrlReplayInit, 0, info.dValue, "");
045.             }
046.          }
047. //+------------------------------------------------------------------+
048.       void SweepAndCloseChart(void)
049.          {
050.             long id;
051.             
052.             if ((id = ChartFirst()) > 0) do
053.             {
054.                if (ChartSymbol(id) == def_SymbolReplay)
055.                   ChartClose(id);
056.             }while ((id = ChartNext(id)) > 0);
057.          }
058. //+------------------------------------------------------------------+
059.    public   :
060. //+------------------------------------------------------------------+
061.       C_Replay()
062.          :C_ConfigService()
063.          {
064.             Print("************** Market Replay Service **************");
065.             srand(GetTickCount());
066.             SymbolSelect(def_SymbolReplay, false);
067.             CustomSymbolDelete(def_SymbolReplay);
068.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
069.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
070.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
071.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
072.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
073.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
074.             SymbolSelect(def_SymbolReplay, true);
075.          }
076. //+------------------------------------------------------------------+
077.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
078.          {
079.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
080.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
081.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
082.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
083.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
084.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
085.             SweepAndCloseChart();
086.             m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
087.             if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl"))
088.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
089.             else
090.                Print("Apply template: ", szNameTemplate, ".tpl");
091. 
092.             return true;
093.          }
094. //+------------------------------------------------------------------+
095.       bool InitBaseControl(const ushort wait = 1000)
096.          {
097.             int handle;
098.             
099.             Print("Waiting for Mouse Indicator...");
100.             Sleep(wait);
101.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
102.             if (def_CheckLoopService)
103.             {
104.                Print("Waiting for Control Indicator...");
105.                if ((handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay)) == INVALID_HANDLE) return false;
106.                ChartIndicatorAdd(m_IdReplay, 0, handle);
107.                IndicatorRelease(handle);
108.                m_IndControl.Position = 0;
109.                m_IndControl.Mode = SHORT_MIN;
110.                UpdateIndicatorControl();
111.             }
112.             
113.             return def_CheckLoopService;
114.          }
115. //+------------------------------------------------------------------+
116.       bool LoopEventOnTime(void)
117.          {         
118.             
119.             while (def_CheckLoopService)
120.             {
121.                UpdateIndicatorControl();
122.                Sleep(250);
123.             }
124.             
125.             return false;
126.          }
127. //+------------------------------------------------------------------+
128.       ~C_Replay()
129.          {
130.             SweepAndCloseChart();
131.             SymbolSelect(def_SymbolReplay, false);
132.             CustomSymbolDelete(def_SymbolReplay);
133.             Print("Finished replay service...");
134.          }
135. //+------------------------------------------------------------------+

Código fuente del archivo C_Replay.mqh

A pesar de que este código no realiza la repetición o simulación como se hacía antes, ya que aún le faltan ciertas cosas, su finalidad es hacer que el servicio de repetición o simulación utilice algunos elementos que no se vieron en el artículo anterior. Entre estos elementos, se encuentra la posibilidad de cargar las barras previas, como se hacía antes, y también las barras que se usarán tanto para la repetición como para la simulación. Aunque en este artículo aún no podremos hacer que las barras de repetición o simulación se utilicen realmente, se cargarán y estarán disponibles cuando el sistema pueda lanzarlas correctamente en el gráfico del activo personalizado.

Hay muchas cuestiones en este código que merecen algún tipo de explicación, ya que muchas de las cosas presentes en él pueden no ser tan claras, incluso para quienes ya tienen una buena experiencia en MQL5. No obstante, la explicación se centrará en quienes deseen comprender los motivos por los que este código se está creando de ese modo.

Al inicio del código, entre las líneas 5 y 11, además de algunas definiciones, se encuentra la declaración e inclusión del archivo compilado del indicador de control dentro del ejecutable del servicio. El motivo de esto ya se explicó y se desarrolló en profundidad en otros artículos de esta misma serie sobre el sistema de repetición/simulador. Por esta razón, solo estoy llamando tu atención sobre estos hechos para que comprendas que no es necesario transportar el archivo del indicador de control.

A continuación, observamos que en la línea 13 hacemos la herencia pública de la clase C_ConfigService. La razón de esto es que no concentraremos todo el trabajo aquí, en la clase C_Replay. Compartiremos parte de la carga con la clase C_ConfigService. Esto refuerza aún más lo que se mostró en el tema anterior, donde expliqué los cambios necesarios para garantizar el encapsulamiento adecuado de la información y las variables.

Así entramos en la parte privada de la clase C_Replay. Esto comienza a partir de la línea 15 y se extiende hasta la línea 58, momento en el que entraremos en la parte pública. Pero primero veamos cómo funciona la parte privada, ya que cuenta con un pequeño conjunto de variables globales que se declaran en las líneas 16 y 21. Sin embargo, atención, en la línea 21 se declara una variable que en realidad es una estructura. Por tanto, esta variable declarada en la línea 21 contiene más información.

Ya en la línea 23, tenemos una pequeña función cuyo único propósito es imprimir un mensaje en el terminal y devolver un valor falso. Pero, ¿por qué estamos devolviendo un valor falso aquí? El motivo es que, si no devolviéramos un valor falso, necesitaríamos una segunda línea para imprimir un error en el terminal. Para que quede más claro, observa la línea 79, donde verificamos una determinada condición. Si tal condición indica que se trata de un error, en la línea 80 deberíamos mostrar un mensaje de error y, a continuación, usar la llamada return para indicar al autor de la llamada que ha habido un error. Esto solo supondría más trabajo. Sin embargo, al usar la función declarada en la línea 23 y devolver un valor falso en ella, podemos imprimir el mensaje deseado y, al mismo tiempo, devolver una indicación de fallo. Por tanto, todo lo que será necesario es usar algo similar a lo que se puede ver en la línea 80. Combinamos las cosas para reducir el trabajo de programación.

No obstante, el código que de hecho debería resultarte más curioso es el que podemos ver entre las líneas 25 y 46. Este código realizará un trabajo de gran importancia para nosotros. Mantendrá y ajustará los datos del indicador de control. Por lo tanto, es importante y necesario que entiendas bien cómo funcionan todas las partes antes de intentar comprender lo que se está haciendo aquí. En caso de duda, revisa los artículos anteriores para comprender cómo el indicador de control se comunicará con el mundo exterior.

En la línea 31, verificamos e intentamos capturar un manejador para poder acceder al indicador de control. Ten en cuenta que, si esta captura falla, no será un desastre completo. Todo lo que hacemos es devolver la llamada al autor, abandonando lo que estábamos haciendo en el resto del procedimiento. No obstante, si el manejador es capturado, primero debemos poner el valor de prueba a cero, como se indica en la línea 32. Es muy importante que se realice realmente dicho reinicio, y la forma de hacerlo puede variar ligeramente, pero debe llevarse a cabo. En la línea 33, verificaremos si el buffer del indicador puede leerse. Si puede leerse, en la línea 34 colocaremos el valor en la variable de testeo y ajuste. Es posible que veas un código ligeramente diferente al ejecutarse en los próximos artículos. Pero no debes sorprenderte ni asustarte. Solo se estarán implementando algunas pequeñas mejoras. Estas no aparecerán en la explicación del artículo. Así es como está siendo hecho ahora.

Ya que el manejador no nos es más necesario, usamos la línea 35 para deshacernos de él. Entramos, así, en la fase de testeo y ajuste de la información que se va a pasar y utilizar. En la línea 36, verificamos si tenemos algún dato válido en el indicador de control. Si el indicador ya contiene datos válidos, almacenaremos si estamos pausados o en modo de ejecución, o, mejor dicho, si estamos o no en “play” en la simulación/repetición. Esto se almacena en la línea 37. Ahora, atención, necesitamos hacer esto antes de cualquier otra cosa. Si lo haces después, podrías acceder inevitablemente a datos ya modificados, lo que comprometerá la memoria de la información. La intención aquí es que el servicio asegure la última forma del indicador de control. Algo que antes se hacía con una variable global del terminal.

Ahora debemos prestar aún más atención a la línea 38. En este caso, probaremos si el contenido del buffer es diferente del sistema de posicionamiento global. Si es diferente, haremos una nueva prueba en la línea 40. Esto tiene como objetivo verificar si el indicador de control ya se ha inicializado y si estamos en modo de reproducción. Si ambas condiciones se cumplen, en la línea 41 almacenaremos el valor que se encuentra en el buffer del indicador. Presta atención a esto, ya que durante la fase en que el indicador de control esté en modo pausado no queremos actualizar los datos aquí. Queremos y debemos dejar que el usuario ajuste y use el indicador de control como desee.

De todas formas, en las líneas 42 y 43 se realiza el montaje de la información que se enviará al indicador. Dicha información se enviará al indicador durante un evento personalizado que se activa en la línea 44. Una vez que se dispare este evento, pasamos el control para que MetaTrader 5 haga su trabajo; sin embargo, el servicio continuará ejecutándose en paralelo.

Este código presente en este procedimiento es algo que debes analizar con mucha atención, hasta que de hecho comprendas lo que está sucediendo. A diferencia de lo que se vio en el artículo anterior, este código es un poco más complejo, aunque haga exactamente lo mismo. Es decir, que cuando MetaTrader 5 coloca el indicador de control en el gráfico, este código lo inicia. Después de esto, se comprobará lo que está ocurriendo en el indicador. Cuando el usuario cambie el marco temporal, el servicio actuará como una memoria de la última forma del indicador y lo inicializará de nuevo, pero tomando como referencia el último estado antes del cambio de marco temporal.

Bien, ahora vamos a ver un código que surgió por cuestiones de reutilización. Esto se observa en la línea 48. El procedimiento simplemente cierra todas las ventanas gráficas de MetaTrader 5 que contengan el activo de repetición. No se trata de algo tan complicado de entender. Pero, como tenemos que realizar esta operación dos veces como mínimo, decidí crear este procedimiento solo para evitar duplicar el código.

Bien. A partir de ahora, entramos en los procedimientos públicos de la clase C_Replay. Básicamente, se puede ver que el código no difiere mucho del que había antes, al menos en lo que respecta al constructor y al destructor de la clase. Por tanto, no haré ningún comentario adicional sobre estos dos, ya que ya fueron debidamente explicados en artículos anteriores, donde expliqué el funcionamiento de la clase C_Replay. Sin embargo, aquí existen tres funciones que merecen al menos algún tipo de explicación. Así que vamos a ellas en el orden en que aparecen en el código.

La primera es OpenChartReplay, que comienza en la línea 77 y finaliza en la línea 93. Esta función verifica la integridad de la información recopilada por el sistema de carga. Esto es necesario para que la repetición o simulación puedan ejecutarse de hecho. Sin embargo, es precisamente en esta función donde encontramos algo bastante complejo que, junto con la función InitBaseControl, que se verá después, nos permite utilizar una plantilla.

Esta cuestión de la plantilla es de suma importancia para nosotros. Necesitamos que se utilice y ponga en funcionamiento correctamente. Pero hacer esto no es algo tan simple. Es diferente de lo que muchos, incluido yo, imaginábamos al principio. En la línea 87, después de haber abierto el gráfico del activo en la línea 86, intentamos agregar la plantilla al gráfico. La plantilla que se debe utilizar se indica como uno de los argumentos de la función. De todas formas, se colocará una plantilla en el gráfico, ya sea la indicada por el usuario o la plantilla predeterminada de MetaTrader 5. Pero hay un detalle aquí que se explora poco o al que ni siquiera se hace mención: la plantilla no se coloca de inmediato. La plantilla no se coloca de inmediato. La función ChartApplyTemplate es asíncrona, es decir, puede ejecutarse algunos milisegundos después de haber sido llamada. Y esto supone un problema para nosotros.

Para entender el tamaño del problema, haremos una breve pausa en la clase C_Replay para poder observar el código del servicio. Este se encuentra justo abajo.

01. //+------------------------------------------------------------------+
02. #property service
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property copyright "Daniel Jose"
05. #property version   "1.58"
06. #property description "Replay-Simulator service for MetaTrade 5 platform."
07. #property description "This is dependent on the Market Replay indicator."
08. #property description "For more details on this version see the article."
09. #property link "https://www.mql5.com/pt/articles/"
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Replay.mqh>
12. //+------------------------------------------------------------------+
13. input string            user00 = "Mini Dolar.txt";   //Replay Configuration File.
14. input ENUM_TIMEFRAMES   user01 = PERIOD_M5;          //Initial Graphic Time.
15. input string            user02 = "Default";          //Template File Name
16. //+------------------------------------------------------------------+
17. C_Replay *pReplay;
18. //+------------------------------------------------------------------+
19. void OnStart()
20. {
21.    pReplay = new C_Replay();
22. 
23.    UsingReplay();   
24.    
25.    delete pReplay;
26. }
27. //+------------------------------------------------------------------+
28. void UsingReplay(void)
29. {
30.    if (!(*pReplay).SetSymbolReplay(user00)) return;
31.    if (!(*pReplay).OpenChartReplay(user01, user02)) return;
32.    if (!(*pReplay).InitBaseControl()) return;
33.    Print("Permission granted. Replay service can now be used...");
34.    while ((*pReplay).LoopEventOnTime());
35. }
36. //+------------------------------------------------------------------+

Código fuente del servicio de repetición/simulador.

Observa que ejecutamos las cosas en una secuencia determinada. Esto ocurre entre las líneas 30 y 34. Nota que, después de inicializar los valores mediante el constructor en la línea 21, en la línea 30 se comprueba si todo está correcto con la carga. Después, en la línea 31, intentamos abrir el gráfico y, solo después, en la línea 32, cargamos los elementos que necesitamos para controlar el servicio. Si todo ocurre bien, en la línea 33 imprimiremos un mensaje en el terminal y, en la línea 34, entraremos en el bucle de ejecución.

Al observar este código, podemos darnos cuenta de que, entre abrir el gráfico (línea 31) y agregar los controles (línea 32), pueden ocurrir cosas extrañas. Esto se debe al uso de la plantilla que se carga en la clase C_Replay. Entonces, volvamos a la clase para entender el verdadero problema que supone el uso de una plantilla.

Después de pedir al MetaTrader 5 que use una plantilla, en la línea 87 de la clase C_Replay, el código se puede ejecutar mucho más rápido de lo que debería. Por esta razón, en la línea 99 informamos al usuario que el servicio espera la presencia del indicador de mouse. Si este indicador está en la plantilla, se cargará automáticamente; de lo contrario, el usuario deberá agregarlo manualmente.

Este problema se debe a que la función responsable de agregar la plantilla se ejecuta de forma asíncrona. Para minimizar los daños, utilizamos la línea 100, donde detenemos el servicio durante un tiempo para no ejecutar nada, esperando que el gráfico se estabilice y que la función de colocación de la plantilla se aplique efectivamente en él. Solo y únicamente después de esto, en la línea 101, verificamos si el indicador de mouse está o no presente. Este bucle se ejecutará hasta que el indicador aparezca en el gráfico o hasta que el gráfico sea cerrado por el usuario.

En cuanto se detecte el indicador de mouse o se cierre el gráfico, el código continuará. Si todo está conforme a lo esperado, en la línea 105 intentaremos agregar el indicador de control al gráfico. Todo esto es muy bonito, pero el indicador de control no puede y no se aceptará si está en la plantilla. Esta es una de las modificaciones que mostraré después cómo hacer, para evitar que el indicador aparezca en la plantilla. También será necesario hacer una pequeña modificación en el indicador de mouse, pero eso queda para después. De todas formas, sin la línea 100, el gráfico se cerraría poco tiempo después de abrirse.


Conclusión

Aunque queda la sensación de que aún no ha terminado, es necesario explicar con detalle por qué el gráfico se cierra y se aplica la plantilla. Es algo bastante complicado y también es necesario mostrar otras cosas para que realmente entiendas cómo es posible y por qué el simple hecho de que exista la línea 100 lo impide. Por tanto, dejaré esta explicación para el próximo artículo, donde detallaré con calma la cuestión de la plantilla y las modificaciones que fueron necesarias en los módulos de los indicadores para que puedan funcionar adecuadamente en este servicio de repetición/simulación.

Como puedes ver, este es diferente del servicio de prueba que se vio en el artículo anterior. Pero antes te dejo con un video que muestra el resultado de la ejecución de este sistema. Como no está de la forma mostrada en el video, no habrá un anexo en este artículo.


Video de demostración


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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Redes neuronales: así de sencillo (Parte 74): Predicción de trayectorias con adaptación Redes neuronales: así de sencillo (Parte 74): Predicción de trayectorias con adaptación
Este artículo presenta un método bastante eficaz de previsión de trayectorias de múltiples agentes, capaz de adaptarse a diversas condiciones ambientales.
Desarrollo de un sistema de repetición (Parte 57): Diseccionamos el servicio de prueba Desarrollo de un sistema de repetición (Parte 57): Diseccionamos el servicio de prueba
Un último detalle: Aunque no se incluye en este artículo, explicaré el código del servicio que se estará utilizando en el próximo, ya que usaremos este mismo código como trampolín para lo que realmente estamos desarrollando. Así que ten un poco de paciencia y espera el próximo artículo, pues las cosas se están poniendo cada día más interesantes.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Desarrollo de un sistema de repetición (Parte 56): Adecuación de los módulos Desarrollo de un sistema de repetición (Parte 56): Adecuación de los módulos
Aunque los módulos se comunican de manera adecuada, existe un error al intentar utilizar el indicador de mouse en el servicio de repetición. Necesitamos corregir esto ahora, antes de pasar al siguiente paso. Además, se ha corregido una incidencia en el código del indicador de mouse. Esta versión finalmente se ha vuelto estable y está debidamente finalizada.