English Русский Português
preview
Desarrollo de un sistema de repetición (Parte 69): Ajuste del tiempo (II)

Desarrollo de un sistema de repetición (Parte 69): Ajuste del tiempo (II)

MetaTrader 5Ejemplos | 12 diciembre 2024, 16:17
130 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 68): Ajuste del tiempo (I)", expliqué la parte del código relacionada con el indicador de mouse. Pero ese código no tiene ningún valor si no se revisa el código del servicio de repetición/simulador. De todas formas, si no has leído el artículo anterior, te sugiero que lo hagas antes de intentar entender este. Esto se debe a que ambos son complementarios.

Por ahora, el enfoque será informar del tiempo restante de la barra cuando el símbolo tenga baja liquidez. Esto ocurre porque, en esos momentos, no se generarán eventos OnCalculate de forma espontánea o, mejor dicho, de manera tradicional. Así, el indicador de mouse no recibirá en absoluto los valores correctos relativos a los segundos transcurridos. Sin embargo, basándonos en lo visto en el artículo anterior, podemos pasar los valores necesarios para que el indicador calcule la cantidad de segundos restantes.

En este primer momento, nos centraremos básicamente en el servicio de repetición/simulador. Para ser más específicos, nos centraremos en el archivo C_Replay.mqh. Comencemos, entonces, a analizar las modificaciones o adiciones realizadas en el código.


Ajustamos el archivo C_Replay.mqh

Los cambios que hay que realizar no son muchos. Sin embargo, harán que el código explicado en el artículo anterior tenga sentido, especialmente la parte relacionada con el uso de la función de biblioteca iSpread en el evento OnCalculate. Probablemente te hayas preguntado por qué utilicé la función iSpread, ya que sería más práctico leer el valor del spread directamente desde el array recibido por la función OnCalculate.

De hecho, esto es algo bastante curioso. Sin embargo, para entender el motivo, necesitamos comprender cómo funcionan las cosas realmente. Para ello, es necesario observar y comprender cómo el código del servicio de repetición/simulador realiza sus funciones. Además, claro, debemos entender cómo MetaTrader 5 maneja la información.

Comencemos por lo más sencillo: comprender el código presente en el archivo C_Replay.mqh. Esto es importante porque este archivo es el responsable de generar la información que se nos presenta en el gráfico. El código modificado puede verse a continuación en su totalidad:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl    "Market Replay Control"
013. #define def_MaxSlider             (def_MaxPosSlider + 1)
014. //+------------------------------------------------------------------+
015. class C_Replay : public C_ConfigService
016. {
017.    private   :
018.       struct st00
019.       {
020.          C_Controls::eObjectControl Mode;
021.          uCast_Double               Memory;
022.          ushort                     Position;
023.          int                        Handle;
024.       }m_IndControl;
025.       struct st01
026.       {
027.          long     IdReplay;
028.          int      CountReplay;
029.          double   PointsPerTick;
030.          MqlTick  tick[1];
031.          MqlRates Rate[1];
032.       }m_Infos;
033.       stInfoTicks m_MemoryData;
034. //+------------------------------------------------------------------+
035. inline bool MsgError(string sz0) { Print(sz0); return false; }
036. //+------------------------------------------------------------------+
037. inline void UpdateIndicatorControl(void)
038.          {
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
045.                   m_IndControl.Memory.dValue = Buff[0];
046.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+
058.       void SweepAndCloseChart(void)
059.          {
060.             long id;
061.             
062.             if ((id = ChartFirst()) > 0) do
063.             {
064.                if (ChartSymbol(id) == def_SymbolReplay)
065.                   ChartClose(id);
066.             }while ((id = ChartNext(id)) > 0);
067.          }
068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074.             static int st_Spread = 0;
075. 
076.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
077.             {
078.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
079.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
080.                {                  
081.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
082.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
083.                   {
084.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
085.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
086.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
087.                   {
088.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
089.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
090.                   }
091.                }
092.                if (bViewTick)
093.                {
094.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
095.                   if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, "");
096.                }
097.                st_Spread = (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time);
098.                m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time);
099.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
100.             }
101.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
102.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
103.             m_Infos.CountReplay++;
104.          }
105. //+------------------------------------------------------------------+
106.       void AdjustViewDetails(void)
107.          {
108.             MqlRates rate[1];
109. 
110.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
111.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
112.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
113.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
114.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
115.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
116.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
117.             if (rate[0].close > 0)
118.             {
119.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
120.                   m_Infos.tick[0].last = rate[0].close;
121.                else
122.                {
123.                   m_Infos.tick[0].bid = rate[0].close;
124.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
125.                }               
126.                m_Infos.tick[0].time = rate[0].time;
127.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
128.             }else
129.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
130.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
131.          }
132. //+------------------------------------------------------------------+
133.       void AdjustPositionToReplay(void)
134.          {
135.             int nPos, nCount;
136.             
137.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
138.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
139.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
140.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
141.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
142.                CreateBarInReplay(false);
143.          }
144. //+------------------------------------------------------------------+
145.    public   :
146. //+------------------------------------------------------------------+
147.       C_Replay()
148.          :C_ConfigService()
149.          {
150.             Print("************** Market Replay Service **************");
151.             srand(GetTickCount());
152.             SymbolSelect(def_SymbolReplay, false);
153.             CustomSymbolDelete(def_SymbolReplay);
154.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
155.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
156.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
157.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
158.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
159.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
160.             SymbolSelect(def_SymbolReplay, true);
161.             m_Infos.CountReplay = 0;
162.             m_IndControl.Handle = INVALID_HANDLE;
163.             m_IndControl.Mode = C_Controls::ePause;
164.             m_IndControl.Position = 0;
165.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
166.          }
167. //+------------------------------------------------------------------+
168.       ~C_Replay()
169.          {
170.             SweepAndCloseChart();
171.             IndicatorRelease(m_IndControl.Handle);
172.             SymbolSelect(def_SymbolReplay, false);
173.             CustomSymbolDelete(def_SymbolReplay);
174.             Print("Finished replay service...");
175.          }
176. //+------------------------------------------------------------------+
177.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
178.          {
179.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
180.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
181.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
182.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
183.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
184.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
185.             SweepAndCloseChart();
186.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
187.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
188.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
189.             else
190.                Print("Apply template: ", szNameTemplate, ".tpl");
191. 
192.             return true;
193.          }
194. //+------------------------------------------------------------------+
195.       bool InitBaseControl(const ushort wait = 1000)
196.          {
197.             Print("Waiting for Mouse Indicator...");
198.             Sleep(wait);
199.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
200.             if (def_CheckLoopService)
201.             {
202.                AdjustViewDetails();
203.                Print("Waiting for Control Indicator...");
204.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
205.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
206.                UpdateIndicatorControl();
207.             }
208.             
209.             return def_CheckLoopService;
210.          }
211. //+------------------------------------------------------------------+
212.       bool LoopEventOnTime(void)
213.          {         
214.             int iPos;
215. 
216.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
217.             {
218.                UpdateIndicatorControl();
219.                Sleep(200);
220.             }
221.             m_MemoryData = GetInfoTicks();
222.             AdjustPositionToReplay();
223.             EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, "");
224.             iPos = 0;
225.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
226.             {
227.                if (m_IndControl.Mode == C_Controls::ePause) return true;
228.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
229.                CreateBarInReplay(true);
230.                while ((iPos > 200) && (def_CheckLoopService))
231.                {
232.                   Sleep(195);
233.                   iPos -= 200;
234.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
235.                   UpdateIndicatorControl();
236.                }
237.             }
238. 
239.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
240.          }
241. };
242. //+------------------------------------------------------------------+
243. #undef def_SymbolReplay
244. #undef def_CheckLoopService
245. #undef def_MaxSlider
246. //+------------------------------------------------------------------+

Código fuente del archivo C_Replay.mqh

En este código mostrado, notarás que hay algunas líneas que fueron destacadas. Estas deben eliminarse del código que existía antes de este artículo. No son muchas líneas, pero las consecuencias generadas serán enormes.

Lo primero que debes notar es que en la línea 74 aparece una nueva variable. Su objetivo es relativamente simple: contar los segundos cuando la liquidez se reduzca drásticamente o sea demasiado baja. Aunque este tipo de operación no se está realizando en este momento exacto, es fundamental entender lo que está sucediendo para comprender cómo se implementará. 

En primer lugar, observa que el evento personalizado ha sido eliminado del código original en la línea 223. Pero presta atención a algo importante: en cada iteración del bucle que comienza en la línea 225 se realiza una llamada a CreateBarInReplay. Esto ocurre en la línea 229. Ahora, fíjate en este detalle: aproximadamente cada 195 milisegundos, debido a la línea 232 y al tiempo necesario para ejecutar las llamadas del bucle en la línea 225, se ejecutará la llamada a CreateBarInReplay. Esto significa que habrá unas cinco llamadas por segundo, siempre que no haya otras intercaladas. En este punto, debes olvidarte de que estamos en un momento de buena liquidez. Intento mostrar cómo funcionará el servicio de repetición/simulador cuando la liquidez sea muy baja. Por lo tanto, guarda este dato: aproximadamente cinco llamadas por segundo a la función CreateBarInReplay.

Ahora volvamos al procedimiento CreateBarInReplay para entender lo que sucede cuando la liquidez es adecuada, es decir, cuando se realizan al menos cinco llamadas cada segundo.

En este caso, la condición de la línea 76 será verdadera. Por lo tanto, se ejecutará el contenido entre las líneas 77 y 100, ten en cuenta que dentro de este rango hay algunas líneas que se eliminaron del código. Puedes comprobarlo viendo las líneas destacadas. Entre estas líneas destacadas se encuentra la 95, que generaba un evento personalizado cada vez que se creaba una nueva barra de un minuto. Este hecho será determinante para que la función iSpread aparezca en el procedimiento OnCalculate. Pero, por ahora, no te preocupes por esto. Primero, comprendamos lo básico. Observa que en la línea 97 se ha añadido un nuevo código que inicializa el valor de la variable.

Ahora presta mucha atención a este hecho: verás que las líneas 98 y 99 están destacadas. Sin embargo, su código no desapareció, simplemente cambió de posición. Antes, estas líneas estaban dentro del bloque de código que se ejecutaba si la condición de la línea 76 era verdadera. Ahora, su ejecución es incondicional porque fueron trasladadas a las líneas 101 y 102. Pero atención: aunque la línea 101 sea diferente, realiza la misma operación que la línea 98, solo que ahora incluye una máscara. Esto permite que el indicador de mouse sepa que el valor del spread proviene del servicio de repetición/simulador. Lo que estamos haciendo es simplemente usar una operación OR para configurar la máscara de manera adecuada. Sin embargo, esto plantea un problema: si el valor de la variable st_Spread invade la región de bits reservada para la máscara, el indicador de mouse no podrá interpretar correctamente los valores que está recibiendo.

Si algo resulta extraño o falla, basta con revisar si el valor de la variable st_Spread invade la región de bits reservada para la máscara. Sin embargo, esto no debería ocurrir en situaciones normales, ya que el servicio de repetición/simulador está diseñado para realizar estudios y análisis intradía. Solo se daría esta condición si el servicio de repetición/simulador se llevara al límite extremo de su funcionamiento en términos de tiempo. De todos modos, para prevenir inconvenientes, puedo adelantarte que el límite de tiempo son casi 12 días en términos de segundos, lo cual es mucho más que suficiente para nuestro propósito.

Continuemos con la explicación de cómo funciona este sistema. Si compilas y ejecutas el servicio de repetición/simulador junto con el código compilado del indicador de mouse presentado en el artículo anterior y el símbolo tiene una liquidez adecuada, es decir, al menos un tick por segundo, obtendrás la actualización correcta del tiempo restante para que finalice la barra actual y comience una nueva.

Muy bien, pero esto no explica por qué no se utilizó el array de spread que está presente en una de las versiones de la llamada OnCalculate. ¿Por qué fue necesario utilizar iSpread para obtener el valor del spread que el servicio está proporcionando, como se observa en la línea 101? Para entender esto, es necesario comprender otro aspecto ahora.


¿Por qué utilizar iSpread?

A la hora de escribir este artículo, la versión más reciente de MetaTrader 5 se puede ver en la siguiente imagen:

Image 01

Hasta esta versión mencionada, y puede que en el momento en que tú, estimado lector, estés leyendo este artículo, MetaTrader 5 todavía maneje las barras, al menos en lo que respecta a los símbolos personalizados, de una forma peculiar. Es posible que no todas las informaciones relacionadas con la barra, pero como estamos transmitiendo información mediante el spread, este comportamiento peculiar se puede verificar.

Para demostrarlo, realizaremos pequeñas modificaciones en el código del archivo de cabecera C_Replay.mqh y en el indicador de mouse. Creo que de esta forma será más fácil explicar lo que realmente sucede, ya que no será suficiente con explicarlo. Por lo tanto, en el archivo C_Replay.mqh, cambia el código en el siguiente fragmento mostrado a continuación:

068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074.             static int st_Spread = 0;
075. 
076.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
077.             {
078.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
079.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
080.                {                  
081.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
082.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
083.                   {
084.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
085.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
086.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
087.                   {
088.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
089.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
090.                   }
091.                }
092.                if (bViewTick)
093.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
094.                st_Spread = (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time);
095.             }
096.             Print(TimeToString(st_Spread, TIME_SECONDS));
097.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
098.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
099.             m_Infos.CountReplay++;
100.          }
101. //+------------------------------------------------------------------+

Fragmento del archivo C_Replay.mqh

Observa que el código de este fragmento ya está depurado, por lo que la numeración de las líneas puede ser ligeramente diferente. Sin embargo, el código será idéntico al que se mostró anteriormente en este artículo. La diferencia está en la línea 96, que se añadió para mostrar en el terminal el valor que estamos asignando al spread de la barra. Como resultado de ejecutar este código modificado, obtendremos lo que se puede observar a continuación:

Anime 01

Observa que el valor mostrado es exactamente igual al valor que se muestra como tiempo en el gráfico de ticks. Es muy importante que te des cuenta de esto. Ahora ya tenemos la confirmación de que el valor que se está colocando en el spread de la barra es, de hecho, el valor del tiempo en el gráfico. Ahora revisemos otro aspecto. Modifiquemos ligeramente el indicador de control, algo muy sutil, solo para analizar cómo funciona el sistema. Esta modificación la realizaremos en el código del archivo de cabecera C_Study.mqh. Esta modificación puede verse a continuación:

109. //+------------------------------------------------------------------+
110.       void Update(const eStatusMarket arg)
111.          {
112.             int i0;
113.             datetime dt;
114.                      
115.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
116.             {
117.                case eCloseMarket :
118.                   m_Info.szInfo = "Closed Market";
119.                   break;
120.                case eInReplay    :
121.                case eInTrading   :
122.                   i0 = PeriodSeconds();
123.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
124.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
125.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time/* - dt*/, TIME_SECONDS);
126.                   break;
127.                case eAuction     :
128.                   m_Info.szInfo = "Auction";
129.                   break;
130.                default           :
131.                   m_Info.szInfo = "ERROR";
132.             }
133.             Draw();
134.          }
135. //+------------------------------------------------------------------+

Fragmento del archivo C_Study.mqh

Observa con mucha atención, ya que se trata de algo muy sutil. En la línea 125 se eliminó el ajuste dt, por lo que ahora se mostrará la información de cuándo se espera una nueva barra. Fíjate en este detalle: no se trata del tiempo restante para una nueva barra, sino de cuándo aparecerá efectivamente esta. A continuación, compilamos nuevamente el indicador de mouse para probar el valor que se mostrará. En la siguiente animación, puedes ver lo que ocurre realmente.

Anime 2

Nota que el período gráfico utilizado es de dos minutos. Por lo tanto, el cálculo realizado indica cuándo surgirá la nueva barra. Esto se muestra en el indicador de mouse. Observa que, cuando el período gráfico alcanza la posición adecuada, el indicador comienza automáticamente a informar de cuándo aparecerá una nueva barra. Es decir, el sistema funciona correctamente. Sin embargo, en estas pruebas no hemos verificado el valor que el servicio de repetición/simulación nos está proporcionando. Lo que estamos haciendo es simplemente comprobar la información que sabíamos que debía estar ahí. Ahora procederemos a verificar el valor que nos está transmitiendo. Debemos asegurarnos de que el período gráfico no sea de un minuto, ya que, de lo contrario, la prueba sería inválida. Mantendremos el período en dos minutos, ya que es adecuado para analizar lo que está ocurriendo.

Para que la prueba se desarrolle como se espera, será necesario realizar un pequeño cambio. Presta atención de nuevo al código que se encuentra en el fragmento siguiente.

109. //+------------------------------------------------------------------+
110.       void Update(const eStatusMarket arg)
111.          {
112.             int i0;
113.             datetime dt;
114.                      
115.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
116.             {
117.                case eCloseMarket :
118.                   m_Info.szInfo = "Closed Market";
119.                   break;
120.                case eInReplay    :
121.                case eInTrading   :
122.                   i0 = PeriodSeconds();
123.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
124.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
125.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)/*m_Info.Rate.time -*/ dt, TIME_SECONDS);
126.                   break;
127.                case eAuction     :
128.                   m_Info.szInfo = "Auction";
129.                   break;
130.                default           :
131.                   m_Info.szInfo = "ERROR";
132.             }
133.             Draw();
134.          }
135. //+------------------------------------------------------------------+

Fragmento del archivo C_Study.mqh

Lo que estamos haciendo ahora es aislar el valor que nos proporciona el servicio y mostrarlo en el indicador de mouse. El resultado se puede observar en la siguiente animación:

Anime 3

Vemos que corresponde exactamente a lo esperado. Ahora no modificaremos este código del archivo de cabecera, pero realizaremos otra acción dentro del indicador de mouse. Examinemos qué ocurriría si utilizáramos el valor del spread obtenido durante la llamada a OnCalculate. Para ello, debemos modificar el código del indicador de mouse. No olvides este detalle: el valor que se mostrará en el indicador será el capturado y almacenado en la variable GL_TimeAdjust. Es muy importante recordar este hecho. Por lo tanto, procederemos a modificar el código del indicador para comprobar si es adecuado usar el valor del spread obtenido en la llamada a OnCalculate. El nuevo código se muestra a continuación.

46. //+------------------------------------------------------------------+
47. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
48.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
49.                 const long& volume[], const int& spread[])
50. //int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[])
51. {
52.    GL_PriceClose = close[rates_total - 1];
53. //   GL_PriceClose = price[rates_total - 1];
54.    GL_TimeAdjust = (spread[rates_total - 1] & (~def_MaskTimeService);
55. //   if (_Symbol == def_SymbolReplay)
56. //      GL_TimeAdjust = iSpread(NULL, PERIOD_M1, 0) & (~def_MaskTimeService);
57.    m_posBuff = rates_total;
58.    (*Study).Update(m_Status);   
59.    
60.    return rates_total;
61. }
62. //+------------------------------------------------------------------+

Fragmento del archivo del indicador de mouse

Observa exactamente lo que estamos haciendo en el fragmento anterior. Estamos aislando el código para que las líneas destacadas correspondan al código actual, tal y como se mostró en el artículo anterior, y deben eliminarse temporalmente. En su lugar, hemos introducido otro código, esta vez con el propósito de usar los datos proporcionados por MetaTrader 5 en la función OnCalculate. Es decir, ahora dejamos de usar la llamada a iSpread. Utilizaremos el valor que MetaTrader 5 nos proporciona en el array de spread. Para mantener la compatibilidad con el servicio, realizamos un pequeño cambio, visible en la línea 54. Observa que esta operación es similar a lo que hacíamos antes cuando usábamos iSpread, pero ahora el valor utilizado proviene del argumento de la llamada a OnCalculate. Esto supondría una gran diferencia para nosotros, ya que elimina la necesidad de realizar una llamada extra solo para obtener el valor que MetaTrader 5 ya proporciona como argumento en OnCalculate.

A continuación, veremos el resultado de la ejecución de este código. Se puede observar en la siguiente animación:

Anime 4

¿Oh? ¿Qué fue eso? ¿Qué ocurrió aquí? ¿Por qué se congeló el valor del indicador de mouse? No es fácil dar una respuesta a estas preguntas. Contrario a lo que algunos podrían pensar, no conozco la respuesta exacta. ¿Cómo es posible que no la sepas? Es cierto que tengo algunas sospechas, pero prefiero mantenerme en silencio entre sospechar y afirmar algo. Considero más adecuado mostrarte algo que quizá no esperabas que ocurriera, y que terminó sucediendo. Así, podrás verificarlo por ti mismo y sacar tus propias conclusiones.

De todas formas, el servicio sigue enviando los valores de la misma manera que se mostró en las animaciones anteriores. El indicador sigue capturando el valor del spread. Sin embargo, ¿por qué el valor se ha congelado en el indicador? Bien. No puedo explicarlo. Lo único que sé es que, cuando aparece una nueva barra en el gráfico (por lo que es necesario trabajar con un período diferente de un minuto para detectar esta falla), el valor del spread se actualiza adecuadamente.

Recordemos nuevamente: lo que estoy mostrando aquí podría no estar ocurriendo en el momento en que tú, estimado lector, leas este artículo. Esto se debe a que es posible que MetaTrader 5 ya haya recibido una actualización que corrija este problema. Mientras tanto, estoy utilizando la función iSpread como solución provisional. Pero tan pronto como se corrija este pequeño problema, dejaremos de usar la llamada a iSpread y utilizaremos el valor proporcionado por MetaTrader 5 a la función OnCalculate. Por lo tanto, no te apegues a ninguna parte del código, ya que, a medida que avancemos, lo iremos mejorando continuamente para optimizarlo. Muy bien, ahora creo que comprendes por qué uso la función iSpread en lugar de utilizar el valor que se proporciona como argumento en la llamada a OnCalculate. Pero aún no hemos terminado. Todavía necesitamos idear una forma de hacer que el servicio nos informe del tiempo restante de una barra cuando la baja liquidez hace que no tengamos ticks, o, mejor dicho, eventos que llamen a la función OnCalculate cada segundo. Para ello, eliminaremos las modificaciones realizadas en este aspecto y explicaremos por qué se utiliza la función iSpread en el indicador. Después, retomaremos el trabajo en el servicio.


Corregimos un fallo de servicio

Desafortunadamente, para que todo funcione cuando el tiempo entre los ticks sea mayor de un segundo, necesitaremos abordar el problema de una manera diferente a la planeada al inicio de este artículo. El detalle es que quería colocar el contador dentro de la rutina de creación de las barras. Sin embargo, se me pasó un detalle crítico: el tiempo. Vuelve al principio del artículo y revisa el código fuente del archivo de cabecera C_Replay.mqh. En la línea 230, tenemos un bucle que detiene el servicio de repetición/simulador durante un período determinado, hasta que pase el tiempo necesario para que surja un nuevo tick. Y aquí es donde surge un problema.

El problema es que, durante todo este tiempo, estuve probando el servicio de repetición/simulación en símbolos con buena liquidez. Es decir, símbolos que, en el historial, tenían un tiempo entre ticks inferior a un segundo. Al comenzar a realizar cambios para considerar la posibilidad de tiempos entre ticks mayores a un segundo, se produjo un error. No es que apareciera de repente. En realidad, ya estaba ahí, pero debido al corto tiempo entre ticks, había pasado desapercibida. Ahora quiero que prestes atención específicamente al bucle que se encuentra entre las líneas 230 y 236. ¿Qué hay de malo allí? El error radica en que no estoy gestionando correctamente la pausa que el usuario pueda querer aplicar al sistema. ¿Cómo es eso? Si el servicio está en un bucle esperando el tiempo hasta el próximo tick, ¿dónde está el problema? En efecto, no hay problema cuando el tiempo de espera es corto. Sin embargo, cuando el tiempo de espera supera un segundo, surge un problema grave.

Supongamos que estás haciendo un replay de datos de Forex. Al inicio de la serie diaria, el tiempo entre ticks suele ser considerablemente largo. Si ejecutas el servicio y este detecta que debe esperar 40 segundos para alcanzar el próximo tick, incluso si aplicas una pausa y mueves el pin del indicador de control a otra posición y luego reanudas el servicio, este no responderá al comando de reanudar. Esto se debe a que el servicio está atrapado en el bucle entre las líneas 230 y 236, esperando a que pasen los 40 segundos detectados. Por lo tanto, lo primero que debemos hacer es solucionar esta situación. Sin embargo, para no perder tiempo solucionando esta situación por separado y luego implementando la funcionalidad que permita mostrar el tiempo restante incluso cuando la liquidez sea baja, haremos ambas cosas de una sola vez. El nuevo código para el archivo C_Replay.mqh puede verse completo a continuación:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl   "Market Replay Control"
013. #define def_MaxSlider             (def_MaxPosSlider + 1)
014. //+------------------------------------------------------------------+
015. class C_Replay : public C_ConfigService
016. {
017.    private   :
018.       struct st00
019.       {
020.          C_Controls::eObjectControl Mode;
021.          uCast_Double               Memory;
022.          ushort                     Position;
023.          int                        Handle;
024.       }m_IndControl;
025.       struct st01
026.       {
027.          long     IdReplay;
028.          int      CountReplay;
029.          double   PointsPerTick;
030.          MqlTick  tick[1];
031.          MqlRates Rate[1];
032.       }m_Infos;
033.       stInfoTicks m_MemoryData;
034. //+------------------------------------------------------------------+
035. inline bool MsgError(string sz0) { Print(sz0); return false; }
036. //+------------------------------------------------------------------+
037. inline void UpdateIndicatorControl(void)
038.          {
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
045.                   m_IndControl.Memory.dValue = Buff[0];
046.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+
058.       void SweepAndCloseChart(void)
059.          {
060.             long id;
061.             
062.             if ((id = ChartFirst()) > 0) do
063.             {
064.                if (ChartSymbol(id) == def_SymbolReplay)
065.                   ChartClose(id);
066.             }while ((id = ChartNext(id)) > 0);
067.          }
068. //+------------------------------------------------------------------+
069. inline int RateUpdate(bool bCheck)
070.          {
071.             static int st_Spread = 0;
072. 
073.             st_Spread = (bCheck ? (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time) : st_Spread + 1);
074.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
075.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
076.             
077.             return 0;
078.          }
079. //+------------------------------------------------------------------+
080. inline void CreateBarInReplay(bool bViewTick)
081.          {
082.             bool    bNew;
083.             double dSpread;
084.             int    iRand = rand();
085.             static int st_Spread = 0;
086. 
087.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
088.             {
089.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
090.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
091.                {                  
092.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
093.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
094.                   {
095.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
096.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
097.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
098.                   {
099.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
100.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
101.                   }
102.                }
103.                if (bViewTick)
104.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
105.                RateUpdate(true);
106.                st_Spread = (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time);
107.             }
108.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
109.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
110.             m_Infos.CountReplay++;
111.          }
112. //+------------------------------------------------------------------+
113.       void AdjustViewDetails(void)
114.          {
115.             MqlRates rate[1];
116. 
117.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
118.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
119.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
120.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
121.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
122.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
123.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
124.             if (rate[0].close > 0)
125.             {
126.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
127.                   m_Infos.tick[0].last = rate[0].close;
128.                else
129.                {
130.                   m_Infos.tick[0].bid = rate[0].close;
131.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
132.                }               
133.                m_Infos.tick[0].time = rate[0].time;
134.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
135.             }else
136.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
137.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
138.          }
139. //+------------------------------------------------------------------+
140.       void AdjustPositionToReplay(void)
141.          {
142.             int nPos, nCount;
143.             
144.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
145.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
146.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
147.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
148.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
149.                CreateBarInReplay(false);
150.          }
151. //+------------------------------------------------------------------+
152.    public   :
153. //+------------------------------------------------------------------+
154.       C_Replay()
155.          :C_ConfigService()
156.          {
157.             Print("************** Market Replay Service **************");
158.             srand(GetTickCount());
159.             SymbolSelect(def_SymbolReplay, false);
160.             CustomSymbolDelete(def_SymbolReplay);
161.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
162.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
163.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
164.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
165.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
166.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
167.             SymbolSelect(def_SymbolReplay, true);
168.             m_Infos.CountReplay = 0;
169.             m_IndControl.Handle = INVALID_HANDLE;
170.             m_IndControl.Mode = C_Controls::ePause;
171.             m_IndControl.Position = 0;
172.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
173.          }
174. //+------------------------------------------------------------------+
175.       ~C_Replay()
176.          {
177.             SweepAndCloseChart();
178.             IndicatorRelease(m_IndControl.Handle);
179.             SymbolSelect(def_SymbolReplay, false);
180.             CustomSymbolDelete(def_SymbolReplay);
181.             Print("Finished replay service...");
182.          }
183. //+------------------------------------------------------------------+
184.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
185.          {
186.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
187.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
188.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
189.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
190.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
191.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
192.             SweepAndCloseChart();
193.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
194.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
195.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
196.             else
197.                Print("Apply template: ", szNameTemplate, ".tpl");
198. 
199.             return true;
200.          }
201. //+------------------------------------------------------------------+
202.       bool InitBaseControl(const ushort wait = 1000)
203.          {
204.             Print("Waiting for Mouse Indicator...");
205.             Sleep(wait);
206.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
207.             if (def_CheckLoopService)
208.             {
209.                AdjustViewDetails();
210.                Print("Waiting for Control Indicator...");
211.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
212.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
213.                UpdateIndicatorControl();
214.             }
215.             
216.             return def_CheckLoopService;
217.          }
218. //+------------------------------------------------------------------+
219.       bool LoopEventOnTime(void)
220.          {         
221.             int iPos, iCycles;
222. 
223.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
224.             {
225.                UpdateIndicatorControl();
226.                Sleep(200);
227.             }
228.             m_MemoryData = GetInfoTicks();
229.             AdjustPositionToReplay();
230.             iPos = iCycles = 0;
231.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
232.             {
233.                if (m_IndControl.Mode == C_Controls::ePause) return true;
234.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
235.                CreateBarInReplay(true);
236.                while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause))
237.                {
238.                   Sleep(195);
239.                   iPos -= 200;
240.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
241.                   UpdateIndicatorControl();
242.                   iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1);
243.                }
244.             }
245. 
246.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
247.          }
248. };
249. //+------------------------------------------------------------------+
250. #undef def_SymbolReplay
251. #undef def_CheckLoopService
252. #undef def_MaxSlider
253. //+------------------------------------------------------------------+

Código fuente del archivo C_Replay.mqh

A continuación, explicaremos cómo funciona este código ahora. Empezaremos por el final, que es donde el servicio presentaba un comportamiento extraño cuando la liquidez era muy baja. Observa que se ha corregido el código en la línea 236. De este modo, se evita que quede bloqueado durante un tiempo de espera alto, lo que antes lo mantenía fuera del alcance del usuario. En este caso, todo lo que fue necesario fue agregar una prueba para verificar si el usuario pausó el sistema. Si esto ocurre, el bucle se finalizará y, cuando la ejecución alcance la línea 233, la función se cerrará y regresará al código principal. Esta, a su vez, hará una nueva llamada a la función y quedará en espera. Sin embargo, esta vez se mantendrá en el bucle de la línea 223, lo que permite al usuario ajustar el PIN en el indicador de control y moverse a una nueva posición más adelantada. Este comportamiento proporciona un funcionamiento interesante cuando el símbolo tiene baja liquidez o está en subasta. Tal vez no entiendas completamente lo que estoy describiendo solo con observar este procedimiento LoopEventOnTime. Pero conforme avancemos con las explicaciones, lo comprenderás mejor.

A continuación, analicemos las modificaciones realizadas para informar del tiempo restante de la barra cuando la liquidez es baja. En la línea 221 se ha añadido una nueva variable que se inicializa en la línea 230. Ahora, presta atención a la línea 242, donde utilizamos esta misma variable. Observa que estamos contando de cero a cuatro y, cuando alcanzamos el valor cuatro, llamamos a RateUpdate. Pero, ¿qué función es RateUpdate? Tranquilo, lector, llegaremos a ello. De momento, fíjate en que la llamada se realiza con un argumento falso y el retorno de la función se asigna a la variable. Esto es importante, así que presta atención a este detalle. ¿Recuerdas que mencioné anteriormente que habría aproximadamente cinco ciclos en un segundo? Precisamente por eso tenemos este contador. La idea es hacer que el indicador de mouse reciba un valor que indique que ha pasado un segundo. Pero no es exactamente un segundo. Es un tiempo aproximado. La razón es que no estamos midiendo con precisión un segundo exacto. Sin embargo, el propósito no es la precisión, sino ofrecer al usuario una idea aproximada de cuánto tiempo resta para que la barra se cierre.

Pasemos a otro punto del mismo código. Vamos ahora al procedimiento que comienza en la línea 80 y observa que hay líneas que han sido eliminadas del código. Vuelve a aparecer una llamada a RateUpdate en el código. Pero fíjate que esta vez el argumento pasado es verdadero. Aquí hay algo diferente: si estamos añadiendo un nuevo tick, el argumento debe ser falso. Si solo estamos actualizando el tiempo, el argumento será falso. Interesante. Veamos ahora este procedimiento RateUpdate, que se encuentra a partir de la línea 69.

La función RateUpdate tuvo que crearse debido a que, durante la actualización del tiempo, existía el riesgo de saltar algunos ticks. Esto se debe a la línea 110. Como no queremos correr ese riesgo, es mejor realizar esta operación en otro lugar, lo que llevó a la creación de esta función. Observa que la variable declarada en la línea 85 se ha movido a la línea 71. Asimismo, el trabajo que se realizaba en las líneas 108 y 109 ahora se hace en las líneas 74 y 75, es decir, esta función es casi una copia de lo que ya existía. La diferencia está en la línea 73, pero debes tener en cuenta que esta función siempre devuelve el valor cero. Esto se debe a que en la línea 242 se espera ese valor.

Pero volvamos a la cuestión de la línea 73. Lo que hace esta línea es bastante curioso. Y el motivo es que no es necesario que el tiempo sea completamente exacto, sino que debe ser lo más preciso posible. Cuando un tick se publica en el gráfico, se llama a la función CreateBarInReplay. En este caso, el valor de st_Spread será el tiempo indicado en el tick. Ahora bien, cuando la llamada proviene de LoopEventOnTime, el valor de st_Spread simplemente se incrementará en uno. Esto equivale a un paso de un segundo. Independientemente del valor de st_Spread, tan pronto como ocurra un nuevo tick, el valor se reajustará al más cercano al real. Si la liquidez es baja durante un período prolongado, por ejemplo, 50 segundos, el cronómetro puede adelantarse o retrasarse ligeramente y notarás que el indicador de mouse mostrará un valor y luego otro ligeramente distinto, con una diferencia de más de un segundo. Esto no debe considerarse un fallo. De hecho, hay una pequeña ventaja: si la liquidez es baja durante varios segundos, puedes pausar y reanudar el servicio de inmediato. El resultado será que el servicio ignorará todo el tiempo que debería haber estado esperando. Interesante, ¿verdad?


Consideraciones finales

Para tener una idea más clara de lo que acabo de mencionar, puedes usar ticks de simulación y repetición de un símbolo con baja liquidez. De todos modos, en el video siguiente podrás ver lo que describí sobre pausar y luego reanudar para evitar esperar varios segundos.

Sin embargo, queda un problema más por resolver dentro de este tema. El problema es lograr que el indicador de mouse nos informe de que el símbolo podría haber entrado en subasta. Esta es una cuestión bastante complicada de explicar, por lo que requeriría un artículo completo dedicado exclusivamente a ella. La solución ya estaría implementada en el indicador de mouse. Lo digo porque, si lo colocas en un gráfico y sigues un símbolo cuyos datos provienen del servidor de trading real, notarás que, cuando el símbolo entra en subasta, el indicador de mouse lo informa. No obstante, en el simulador de repetición, donde usamos un símbolo personalizado, existe un problema que complica las cosas. Pero este será el tema del próximo artículo. Nos vemos allí.


Video de demostración

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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Desarrollo de un sistema de repetición (Parte 70): Ajuste del tiempo (III) Desarrollo de un sistema de repetición (Parte 70): Ajuste del tiempo (III)
En este artículo, mostraré cómo utilizar la función CustomBookAdd de manera correcta y funcional. Aunque pueda parecer sencillo, tiene muchas implicaciones. Por ejemplo, permite indicar al indicador de mouse si el símbolo personalizado está en subasta, en negociación o si el mercado está cerrado. El contenido expuesto aquí tiene como único objetivo ser didáctico. En ningún caso debe considerarse una aplicación cuya finalidad sea distinta a la de aprender y estudiar los conceptos mostrados.
Cómo ver las transacciones directamente en el gráfico sin tener que perderse en el historial de transacciones Cómo ver las transacciones directamente en el gráfico sin tener que perderse en el historial de transacciones
En este artículo, crearemos una herramienta sencilla para visualizar cómodamente posiciones y transacciones directamente en el gráfico con navegación mediante teclas. Esto permitirá a los operadores examinar visualmente las transacciones individuales y recibir toda la información sobre los resultados comerciales directamente en el momento.
Desarrollo de un sistema de repetición (Parte 71): Ajuste del tiempo (IV) Desarrollo de un sistema de repetición (Parte 71): Ajuste del tiempo (IV)
En este artículo, mostraré cómo implementar lo presentado en el artículo anterior en el servicio de repetición/simulación. Pero, como suele ocurrir con muchas cosas en la vida, es habitual que surjan problemas. Y este caso no fue una excepción. Sigue leyendo y descubre cuál será el tema del próximo artículo de esta serie. El contenido expuesto aquí tiene como único propósito la enseñanza. En ningún caso debe considerarse una aplicación cuyo objetivo no sea el aprendizaje y el estudio de los conceptos mostrados.
Desarrollo de Sistemas Avanzados de Trading ICT: Implementación de señales en un indicador de Order Blocks Desarrollo de Sistemas Avanzados de Trading ICT: Implementación de señales en un indicador de Order Blocks
En este artículo, aprenderás a desarrollar un indicador de Order Blocks basado en el volumen de la profundidad de mercado y a optimizarlo mediante buffers para mejorar su precisión. Concluimos esta fase del proyecto y nos preparamos para las siguientes, en las que implementaremos una clase de gestión de riesgos y un bot de trading que aprovechará las señales generadas por el indicador.