
Desarrollo de un sistema de repetición (Parte 62): Presionando play en el servicio (III)
Introducción
En el artículo anterior "Desarrollando un sistema de repetición (Parte 61): Presionando play en el servicio (II)", expliqué un problema que estamos enfrentando con el sistema en el momento de usar el modo simulación. Este problema no surge necesariamente de una falla catastrófica en la aplicación que estamos desarrollando, sino por la velocidad de respuesta que el sistema ha mostrado hasta ahora. Este tiempo de respuesta no ha resultado adecuado para que la aplicación pueda procesar todos los datos que se le están enviando. Por esta razón, es necesario que adaptemos las cosas de alguna manera. Aunque nuestro servicio pueda quedar algo distante de una realidad perfecta, sabemos que tal perfección no se presenta en la práctica.
La mejor solución que se me ocurrió fue ajustar los límites máximos que pueden aparecer en la simulación. Sin embargo, en el artículo explicaré con más detalle las implicaciones de estos cambios y por qué elegí este camino específico. Además de esto, existe otro aspecto. Este segundo está relacionado directamente con los datos reales o simulados fuera de la aplicación que estamos desarrollando. Por extraño que parezca, en algunos casos, especialmente con contratos futuros, puede ocurrir que haya una cantidad muy alta de ticks o transacciones en una barra de un minuto. Cuando esto sucede, incluso estando conectados al servidor de trading, nos encontramos con problemas relacionados con la velocidad de respuesta y muestra de los movimientos de precios por parte de la plataforma MetaTrader 5. Si nunca te has encontrado con este tipo de situación, quizá pienses que es un problema relacionado con el equipo que estás usando para ejecutar MetaTrader 5 o con algún fallo en el sistema operativo. Pero lamento decirte que es una completa tontería difundida por personas que en realidad no entienden nada de informática.
Así, si incluso conectados a un servidor de trading real enfrentamos estos problemas, en los que la plataforma no puede procesar la inmensa cantidad de información que está llegando, ¿qué se puede esperar al realizar una repetición de esos datos? Será un desastre total, ya que la calidad del temporizador se verá seriamente comprometida. Por lo tanto, también estableceremos un límite para los datos reales o simulados fuera de la plataforma. Precisamente para evitar que problemas como la dificultad de la plataforma para procesar los datos lleguen a notarse o incluso a surgir. Ahora veremos cómo quedará el nuevo código.
Un nuevo concepto. Un nuevo sistema
Quizás el título de este tema no sea del todo representativo ni explique completamente lo que vamos a hacer. Sin embargo, primero revisaremos qué ha cambiado en la clase responsable de generar y modelar cualquier simulación para nuestro sistema. Pero antes de ver el código de la clase de simulación, necesitamos examinar el archivo de definición, ya que se ha añadido una línea que se utilizará en varios puntos del código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaxTicksVolume 2000 16. //+------------------------------------------------------------------+ 17. union uCast_Double 18. { 19. double dValue; 20. long _long; // 1 Information 21. datetime _datetime; // 1 Information 22. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 23. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 24. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 25. }; 26. //+------------------------------------------------------------------+ 27. enum EnumEvents { 28. evHideMouse, //Hide mouse price line 29. evShowMouse, //Show mouse price line 30. evHideBarTime, //Hide bar time 31. evShowBarTime, //Show bar time 32. evHideDailyVar, //Hide daily variation 33. evShowDailyVar, //Show daily variation 34. evHidePriceVar, //Hide instantaneous variation 35. evShowPriceVar, //Show instantaneous variation 36. evSetServerTime, //Replay/simulation system timer 37. evCtrlReplayInit, //Initialize replay control 38. }; 39. //+------------------------------------------------------------------+
Código fuente del archivo Defines.mqh
La línea 15 es la que se ha añadido al código. No voy a explicar aquí el motivo de este valor, ya que se verá cuando lo utilicemos en el código de simulación. Este será el primer lugar donde se utilice. Ahora que hemos revisado este cambio, podemos pasar al código fuente de la clase responsable de simular los ticks. Dicho código, ya modificado, se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Simulation 007. { 008. private : 009. //+------------------------------------------------------------------+ 010. int m_NDigits; 011. bool m_IsPriceBID; 012. double m_TickSize; 013. struct st00 014. { 015. bool bHigh, bLow; 016. int iMax; 017. }m_Marks; 018. //+------------------------------------------------------------------+ 019. template < typename T > 020. inline T RandomLimit(const T Limit01, const T Limit02) 021. { 022. T a = (Limit01 > Limit02 ? Limit01 - Limit02 : Limit02 - Limit01); 023. return (Limit01 >= Limit02 ? Limit02 : Limit01) + ((T)(((rand() & 32767) / 32737.0) * a)); 024. } 025. //+------------------------------------------------------------------+ 026. inline void Simulation_Time(const MqlRates &rate, MqlTick &tick[]) 027. { 028. for (int c0 = 0, iPos, v0 = (int)(60000 / m_Marks.iMax), v1 = 0, v2 = v0; c0 <= m_Marks.iMax; c0++, v1 = v2, v2 += v0) 029. { 030. iPos = RandomLimit(v1, v2); 031. tick[c0].time = rate.time + (iPos / 1000); 032. tick[c0].time_msc = iPos % 1000; 033. } 034. } 035. //+------------------------------------------------------------------+ 036. inline void CorretTime(MqlTick &tick[]) 037. { 038. for (int c0 = 0; c0 <= m_Marks.iMax; c0++) 039. tick[c0].time_msc += (tick[c0].time * 1000); 040. } 041. //+------------------------------------------------------------------+ 042. inline int Unique(const double price, const MqlTick &tick[]) 043. { 044. int iPos = 1; 045. 046. do 047. { 048. iPos = (m_Marks.iMax > 20 ? RandomLimit(1, m_Marks.iMax - 1) : iPos + 1); 049. }while ((m_IsPriceBID ? tick[iPos].bid : tick[iPos].last) == price); 050. 051. return iPos; 052. } 053. //+------------------------------------------------------------------+ 054. inline void MountPrice(const int iPos, const double price, const int spread, MqlTick &tick[]) 055. { 056. if (m_IsPriceBID) 057. { 058. tick[iPos].bid = NormalizeDouble(price, m_NDigits); 059. tick[iPos].ask = NormalizeDouble(price + (m_TickSize * spread), m_NDigits); 060. }else 061. tick[iPos].last = NormalizeDouble(price, m_NDigits); 062. } 063. //+------------------------------------------------------------------+ 064. inline void Random_Price(const MqlRates &rate, MqlTick &tick[]) 065. { 066. for (int c0 = 1; c0 < m_Marks.iMax; c0++) 067. { 068. MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick); 069. m_Marks.bHigh = (rate.high == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bHigh; 070. m_Marks.bLow = (rate.low == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bLow; 071. } 072. } 073. //+------------------------------------------------------------------+ 074. inline void DistributeVolumeReal(const MqlRates &rate, MqlTick &tick[]) 075. { 076. for (int c0 = 0; c0 <= m_Marks.iMax; c0++) 077. { 078. tick[c0].volume_real = 1.0; 079. tick[c0].volume = 1; 080. } 081. if ((m_Marks.iMax + 1) < rate.tick_volume) for (int c0 = (int)(rate.tick_volume - m_Marks.iMax); c0 > 0; c0--) 082. tick[RandomLimit(0, m_Marks.iMax - 1)].volume += 1; 083. for (int c0 = (int)(rate.real_volume - m_Marks.iMax); c0 > 0; c0--) 084. tick[RandomLimit(0, m_Marks.iMax)].volume_real += 1.0; 085. } 086. //+------------------------------------------------------------------+ 087. inline int RandomWalk(int In, int Out, const double Open, const double Close, double High, double Low, const int Spread, MqlTick &tick[], int iMode, int iDesloc) 088. { 089. double vStep, vNext, price, vH = High, vL = Low; 090. char i0 = 0; 091. 092. vNext = vStep = (Out - In) / ((High - Low) / m_TickSize); 093. for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++) 094. { 095. price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -iDesloc : iDesloc)); 096. price = (price > vH ? vH : (price < vL ? vL : price)); 097. MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick); 098. switch (iMode) 099. { 100. case 1: 101. i0 |= (price == High ? 0x01 : 0); 102. i0 |= (price == Low ? 0x02 : 0); 103. vH = (i0 == 3 ? High : vH); 104. vL = (i0 ==3 ? Low : vL); 105. break; 106. case 0: 107. if (price == Close) return c0; 108. default: 109. break; 110. } 111. if (((int)floor(vNext)) >= c1) continue; else if ((++c2) <= 3) continue; 112. vNext += vStep; 113. vL = (iMode != 2 ? (Close > vL ? (i0 == 3 ? vL : vL + m_TickSize) : vL) : (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize))); 114. vH = (iMode != 2 ? (Close > vL ? vH : (i0 == 3 ? vH : vH - m_TickSize)) : (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH))); 115. } 116. 117. return Out; 118. } 119. //+------------------------------------------------------------------+ 120. public : 121. //+------------------------------------------------------------------+ 122. C_Simulation(const int nDigits) 123. { 124. m_NDigits = nDigits; 125. m_IsPriceBID = (SymbolInfoInteger(def_SymbolReplay, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_BID); 126. m_TickSize = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 127. } 128. //+------------------------------------------------------------------+ 129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume = def_MaxTicksVolume) 130. { 131. int i0, i1, i2; 132. bool b0; 133. 134. m_Marks.iMax = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume)); 135. m_Marks.iMax = ((int)rate.tick_volume > m_Marks.iMax ? m_Marks.iMax : (int)rate.tick_volume - 1); 136. m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); 137. m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); 138. Simulation_Time(rate, tick); 139. MountPrice(0, rate.open, rate.spread, tick); 140. if (m_Marks.iMax > 10) 141. { 142. i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); 143. i1 = m_Marks.iMax - i0; 144. i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); 145. i2 = (i2 == 0 ? 1 : i2); 146. b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); 147. i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 148. RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, 1, i2); 149. RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); 150. m_Marks.bHigh = m_Marks.bLow = true; 151. 152. }else Random_Price(rate, tick); 153. if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); 154. if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); 155. if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); 156. MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); 157. CorretTime(tick); 158. 159. return m_Marks.iMax; 160. } 161. //+------------------------------------------------------------------+ 162. }; 163. //+------------------------------------------------------------------+
Código fuente del archivo C_Simulation.mqh
Aquí probablemente no notarás ningún cambio. Más aún si tenemos en cuenta que hace bastante tiempo que no intervenimos en esta clase. La última vez que trabajamos en ella fue cuando se publicó el artículo sobre el Random Walk. Sin embargo, debido a que necesitamos que el sistema mantenga cierta consistencia temporal, incluso con una degradación en el número de ticks generados, fue necesario modificar ligeramente este código.
Para que no te limites a mirar el código sin entender qué ha cambiado, te mostraré uno de los puntos destacados. Aunque hay otros pequeños cambios en ciertas secciones, no merecen especial atención, ya que se realizaron para adaptar el proceso a una nueva metodología. En cualquier caso, el cambio principal en este caso fue una adición en la línea 16. Esta variable no existía en el código original de la clase. Antes de analizar las implicaciones de esta variable en todo el código de la clase, veamos dónde se inicializa. Es probable que te equivoques al intentar adivinar dónde se inicializa, ya que podrías pensar que es en el constructor de la clase. Pero no es así. Se inicializa en otro punto, más precisamente entre las líneas 134 y 135, por lo que debes prestar mucha atención a lo que ocurre en estas dos líneas, ya que será crucial si deseas modificar el sistema. En la línea 129 se encuentra la declaración de la función encargada de crear la simulación de los ticks. Sin embargo, ahora incluye un parámetro adicional que indica precisamente el número máximo de ticks que se simularán. ¿Recuerdas la línea añadida en el archivo Defines.mqh? Uno de los lugares donde se utiliza esa definición es precisamente aquí. Veamos qué está ocurriendo. Esto es clave para que, si deseas modificar algo, entiendas cómo afectarán esos cambios al comportamiento del código. Al realizar la llamada para efectuar la simulación de los ticks, también deberás proporcionar un valor que indique la cantidad máxima de ticks que se deben simular. Este valor no es obligatorio, ya que tiene uno predeterminado. Sin embargo, si el valor proporcionado es igual o menor que cero, asumiremos que el máximo de ticks será uno. Este valor no es arbitrario. Tiene su razón de ser, ya que, en el caso del Forex, el menor valor posible que puede considerarse como volumen de ticks es precisamente uno. Por otro lado, si proporcionas un valor superior al definido en el archivo de cabecera Defines.mqh, la clase ignorará el valor proporcionado y usará el valor máximo definido en el sistema. Este valor, que se encuentra en el archivo Defines.mqh, representa el número máximo de ticks que se simularán internamente. Sin embargo, cualquier valor dentro de este rango será utilizado como el máximo de ticks simulados. Así, podrás ajustarlo dentro de estos límites.
Ahora, un detalle: este valor máximo definido no se eligió al azar. Al dividir la barra de un minuto por este valor, que en este caso es de dos mil, se genera un tick aproximadamente cada 30 milisegundos. Este intervalo es bastante adecuado para que el movimiento sea suave y constante durante todo el proceso de trazado.
Puedes usar valores mayores, pero ten en cuenta que este no será el valor simulado, sino el valor máximo que puede ser simulado. Esta explicación se aplica a la línea 134. Pero el valor real se define efectivamente en la línea 135. Cuando se ajuste el número máximo real de ticks a simular en la línea 135, se comparará el valor calculado en la línea 134 con el valor presente en la información de la barra. Si el valor de la línea 134 es menor que el de la barra, se utilizará este último. Sin embargo, si el valor de la barra es menor, el valor proporcionado para la simulación será ignorado y el número máximo será el indicado en la barra.
Como mencioné al principio, este tipo de modificación hizo que todos los tests relacionados con el número máximo de ticks que se pueden generar fueran revisados. Por tanto, las funciones y procedimientos de esta clase han experimentado ligeros cambios. Dado que son fáciles de comprender, no entraré en detalles sobre su funcionamiento. Si tienes dudas, consulta el artículo sobre el Random Walk. Este se encuentra en "Desarrollo de un sistema de repetición (Parte 15): El nacimiento del SIMULADOR (V) RANDOM WALK".
Muy bien. Con esto resuelto, ya hemos solucionado la primera cuestión. Es decir, ahora siempre que realicemos una simulación, podremos ajustar el número máximo de ticks necesarios para generar la barra. Sin embargo, esto no es suficiente para lo que buscamos. Recuerda: estamos controlando únicamente la parte de la simulación. Aun así, no le ofrecemos al usuario la posibilidad de ajustar las configuraciones para establecer una cantidad menor de ticks simulados. Por lo tanto, todavía tenemos dos problemas por resolver. Ambos requieren algún tipo de modificación en el sistema.
Vamos entonces a dividir para conquistar. Así, lo próximo que resolveremos será el tema relacionado con el límite máximo de ticks reales o simulados externamente que podrán estar presentes. Para ello, abordaremos un nuevo aspecto.
Ajustamos las configuraciones para datos reales
Aunque el título sugiera que solo se podrán usar datos reales del mercado, esto no es del todo cierto. Puedes simular los movimientos del mercado de forma externa, guardar los datos en un archivo y luego usarlo como un archivo que contiene ticks en lugar de barras. Este tipo de práctica es perfectamente válida y factible. Sin embargo, el tema es el mismo que se discutió en la sección anterior. Es decir, de alguna manera limitaremos el número máximo de ticks que estarán presentes, incluso cuando usemos datos reales del mercado.
A diferencia de lo tratado en el tema anterior, este problema es considerablemente más complejo de resolver de lo que podría parecer. Esto se debe a la propia naturaleza de lo que vamos a manejar. Si el servicio de repetición/simulación estuviera diseñado para usarse exclusivamente con un tipo específico de mercado, el problema sería más fácil de solucionar. En tal caso, podríamos realizar un análisis durante la carga de los ticks y verificar si la cantidad dentro de una ventana de tiempo supera un valor predefinido. Si esto llegara a suceder, forzaríamos al sistema a rechazar los ticks dentro de esa ventana, equivalente a una barra de un minuto. A continuación, obligaríamos al simulador a crear el movimiento basado en el RANDOM WALK. De esta forma, se lograría fácilmente la limitación del número de ticks dentro de la ventana. Sin embargo, debido a que durante la carga de los ticks no sabemos si estaremos lidiando con valores que usarán la graficación BID o LAST, tenemos un problema serio y complejo que resolver. La complicación radica precisamente en no saber si el sistema de graficación es BID o LAST.
Ahora surge una de las primeras preguntas que hay que considerar: ¿cuál sería la mejor solución para este problema? ¿Permitir que el usuario informe del tipo de graficación, con el riesgo de que lo haga incorrectamente, o modificar el código para que sea capaz de manejar esta situación? Aunque la solución de permitir que el usuario indique el tipo de gráfico es considerablemente más sencilla de implementar, tiene el inconveniente de que el usuario podría indicar erróneamente el tipo de gráfico que se debe usar. Seamos sinceros: ¿cuántos usuarios conoces que saben que existen dos tipos de representación gráfica y que uno corresponde a un modelo de mercado determinado y el otro a uno completamente diferente? La mayoría de los usuarios ni siquiera sabe que existen tres tipos de servidores con los que puede comunicarse MetaTrader 5. Mucho menos se les ocurriría configurar el tipo de representación que el símbolo realmente utiliza.
Por tanto, debido a este posible problema, tendremos que esforzarnos un poco más en la implementación de lo que se necesita hacer. De todos modos, ya tenemos un punto de partida: la función LoadTicks de la clase C_FileTicks. Muy bien, veamos la función original y reflexionemos sobre lo que debemos implementar. La función original se muestra en el siguiente fragmento:
01. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true) 02. { 03. int MemNRates, 04. MemNTicks; 05. datetime dtRet = TimeCurrent(); 06. MqlRates RatesLocal[], 07. rate; 08. bool bNew; 09. 10. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 11. MemNTicks = m_Ticks.nTicks; 12. if (!Open(szFileNameCSV)) return 0; 13. if (!ReadAllsTicks()) return 0; 14. rate.time = 0; 15. for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++) 16. { 17. if (!BuildBar1Min(c0, rate, bNew)) continue; 18. if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 19. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 20. } 21. if (!ToReplay) 22. { 23. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 24. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 25. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 26. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 27. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 28. m_Ticks.nTicks = MemNTicks; 29. ArrayFree(RatesLocal); 30. }else SetSymbolInfos(); 31. m_Ticks.bTickReal = true; 32. 33. return dtRet; 34. };
Fragmento de código fuente C_FilesTicks.mqh
No te preocupes por la numeración de las líneas. Aquí se usa solo como referencia para la explicación; no corresponden a las líneas reales del archivo C_FileTicks.mqh, ya que este archivo ha sufrido algunas modificaciones que se verán más adelante. Ahora veremos qué ocurre durante la lectura de los ticks reales.
Cuando la función comienza, en las líneas 10 y 11 se almacenan temporalmente los valores actuales de las posiciones de ticks cargados y de las barras que los representan. Si el autor de la llamada indica que los ticks no se usarán como base del replay, sino como barras previas, esos valores se repondrán en las líneas 27 y 28, respectivamente. Así, el sistema permanecerá intacto hasta que se reciban los ticks de repetición.
En la línea 12, intentaremos abrir el archivo que contiene los datos y, en la línea 13, leeremos todos los ticks que haya en el archivo. Si la lectura es exitosa, habremos cargado todos los ticks. Sin embargo, puede suceder que, en algunos casos, tengamos más ticks por unidad de tiempo que el sistema permite como máximo. Hasta este punto, no podemos hacer nada al respecto. Esto se debe precisamente a que no sabemos qué tipo de representación gráfica se utilizará. Pero, una vez que hayamos leído todos los ticks, conoceremos el tipo de graficación y podremos empezar a trabajar en una solución.
Ahora llega la parte interesante. Analizaremos una ventana de tiempo de un minuto. Está bien. En la línea 15 entramos en un bucle cuyo propósito es construir las barras de un minuto para que el sistema pueda acceder a ellas cuando sea necesario. Aquí es donde intervendremos. Esto se debe a que, en la línea 17, se realiza la llamada que las construye. Dicha función contabiliza el volumen de ticks utilizados para generar el movimiento dentro de la barra. Ahora presta mucha atención: cuando la prueba en la línea 18 indique una condición verdadera, los datos de la barra se expresarán de la misma manera que si se hubiera leído la barra de un archivo. Y este es precisamente el dato que necesitamos pasar a la clase de simulación. Puedes comprobarlo volviendo al tema anterior y revisando la línea 129.
¿Has entendido la idea de lo que necesitamos implementar? Esto se aplicará cuando se verifique que el número de ticks es mayor que el valor definido internamente en el programa, al menos en esta primera fase de la implementación. Posteriormente, realizaremos modificaciones sobre esto.
Esta es la parte fácil: probar y, si es necesario, solicitar a la clase de simulación que realice un RANDOM WALK para que obtengamos un movimiento con el número adecuado de ticks. Pero ahora viene la parte complicada. La clase de simulación de ticks generará los ticks, pero necesitamos encontrar la manera más sencilla posible de modificar los ticks ya cargados. Sin embargo, solo eliminaremos los ticks que forman parte de la ventana en la que el número de ticks supera el valor indicado internamente o definido en la aplicación. Además de este problema, tenemos otro, aunque más sencillo de resolver. La simulación solo tiene que ejecutarse cuando los datos vayan a utilizarse en la repetición. Pero, como mencioné, este problema es fácil de manejar, ya que el autor de la llamada nos informa si los datos se utilizarán o no en la repetición. Todo lo que necesitamos hacer es verificar el valor de ToReplay. Pan comido. Ahora intentemos resolver la parte complicada, que consiste en sobrescribir de alguna manera los ticks innecesarios. Para ello, tendremos que transformar la función mostrada en el fragmento anterior en otra diferente.
El primer intento para que las cosas funcionen —y sí, el desarrollo de nuevas funcionalidades siempre se basa en ensayo y error— se muestra en el siguiente fragmento:
01. //+------------------------------------------------------------------+ 02. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 03. { 04. int MemNRates, 05. MemNTicks, 06. nDigits, 07. nShift; 08. datetime dtRet = TimeCurrent(); 09. MqlRates RatesLocal[], 10. rate; 11. MqlTick TicksLocal[]; 12. bool bNew; 13. 14. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 15. nShift = MemNTicks = m_Ticks.nTicks; 16. if (!Open(szFileNameCSV)) return 0; 17. if (!ReadAllsTicks()) return 0; 18. rate.time = 0; 19. nDigits = SetSymbolInfos(); 20. ArrayResize(TicksLocal, def_MaxSizeArray); 21. m_Ticks.bTickReal = true; 22. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 23. { 24. if (!BuildBar1Min(c0, rate, bNew)) continue; 25. if (bNew) 26. { 27. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 28. { 29. nShift = MemShift; 30. C_Simulation *pSimulator = new C_Simulation(nDigits); 31. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) < 0) return 0; 32. ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 33. nShift += c1; 34. delete pSimulator; 35. } 36. MemShift = nShift; 37. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 38. }; 39. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 40. } 41. ArrayFree(TicksLocal); 42. if (!ToReplay) 43. { 44. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 45. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 46. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 47. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 48. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 49. m_Ticks.nTicks = MemNTicks; 50. ArrayFree(RatesLocal); 51. }else m_Ticks.nTicks = nShift; 52. 53. return dtRet; 54. }; 55. //+------------------------------------------------------------------+
Fragmento del código fuente de la clase C_FileTicks.mqh
Aquí se han añadido varias cosas. Además, se han realizado algunas modificaciones en la secuencia de ejecución de ciertos comandos. Aunque este fragmento parezca ser la solución, aún contiene una pequeña falla. Pero antes de llegar a eso, veamos cómo este fragmento del código, presente en la clase C_FileTicks, logra lidiar con la cuestión de no permitir que el número de ticks reales supere el valor definido internamente por el sistema.
Para lograr esto, fue necesario añadir las líneas 6, 7 y 11, que son nuevas variables que realmente necesitaremos. En la línea 15 iniciamos la primera de nuestras nuevas variables, precisamente para saber dónde se encuentra el contador actual de ticks cargados. También fue necesario incluir las líneas 18 y 19, que simplemente inicializan algunos valores. Sin embargo, también se añadió la línea 20, donde se asigna un espacio de memoria para los ticks que se van a simular. Esta región de memoria se liberará únicamente en la línea 41. Encontramos un pequeño error que corregiremos en el código final relacionado con la línea 31. Pero no te preocupes, llegaremos a eso.
En la línea 21 encontramos algo muy importante. En este fragmento no es evidente el motivo, pero si el contenido de esta línea 21 estuviera en su posición original en el código, no se podría realizar lo que se necesita. Si revisas el fragmento original, mostrado anteriormente, verás que esta línea 21 aparecía originalmente en la línea 31. Sin embargo, cuando se ejecuta la línea 24, la función BuildBar1Min no podrá establecer los criterios necesarios. Por esta razón, las cosas deben realizarse en el orden mostrado en este nuevo fragmento.
De todos modos, lo explicado como necesario está implementado entre las líneas 27 y 35. Esto corresponde a la simulación de los ticks que se utilizarán cuando la cantidad de ticks en una barra de un minuto supere un valor determinado. Este valor está definido internamente en la aplicación.
Ahora presta atención. En la línea 27 hay dos pruebas. La primera verifica si los datos se usarán en la repetición o como barras previas. Si van a usarse como barras previas, no será necesario realizar la simulación. En el segundo test, verificamos si el número de ticks excede el valor definido. Sin embargo, dado que este segundo test podría referirse a un índice negativo, en el primer test hacemos una verificación previa para evitar tal inconveniente. Así, al realizar el segundo test, nos estaremos refiriendo al índice actual, cuyo volumen de ticks acaba de ser contabilizado.
Pero espera un momento. La prueba realizada en la línea 25 solo permitirá que estas verificaciones recién descritas ocurran cuando se detecte una nueva barra. ¿Cómo es que estamos mirando el valor contabilizado de la barra si ya estamos en una nueva barra? Si has pensado esto, debo felicitarte, ya que indica que realmente estás comprendiendo el código. Pero te has dejado un pequeño y crucial detalle. Hasta que se ejecute la línea 39, estaremos observando la barra que acaba de cerrarse. La línea 39 es la que provoca que pasemos a una nueva barra. ¿Entiendes ahora por qué las cosas deben hacerse en una secuencia específica?
Muy bien, pero centrémonos de nuevo en la simulación. Todas las demás líneas, excepto la 51, funcionan como se describió en artículos anteriores de esta serie. La línea 51 depende de lo que ocurra durante la fase de simulación de ticks. Por lo tanto, presta mucha atención, ya que hay un fallo, aunque no afecta de manera significativa a las pruebas de esta función.
En la línea 29, hacemos que el punto de desplazamiento señale el inicio de las posiciones donde comenzarán los ticks de la barra que serán reemplazados. A continuación, iniciamos el sistema de simulación en la línea 30. En la línea 31, ejecutamos la simulación propiamente dicha. Observa que, si falla, la línea 41 no se ejecutará y la memoria asignada no se liberará. Este pequeño problema se corregirá más adelante, pero no afecta en absoluto a la plataforma para las pruebas. Si la simulación de los ticks tiene éxito, hacemos uso de una llamada en la línea 32. Ahora, atención a lo siguiente: esta línea 32 podría sustituirse por un bucle for, pero, dado que es muy probable que la rutina de la biblioteca de MQL5 esté optimizada para mover los datos rápidamente, es preferible usarla en lugar de implementarla aquí. En la línea 33, actualizamos el valor del desplazamiento para señalar una nueva posición inmediatamente después de transferir los datos. Finalmente, en la línea 34, destruimos el simulador.
Para que la memoria de posición se actualice y en la línea 29 apuntemos al lugar correcto, tenemos la línea 36, que se encargará de esta actualización. Sin embargo, todo este procedimiento presenta algunos fallos menores que serán corregidos en el próximo artículo, ya que también tendremos que resolver otro problema que permanece oculto en la fase de simulación.
Conclusión
A pesar de los grandes avances que hemos logrado al limitar el número de ticks en una barra de un minuto, estos avances han traído consigo algunos problemas y han revelado otros. Sin embargo, dado que el contenido de este artículo ya es, en mi opinión, bastante denso, y requiere que lo estudies detenidamente para entender lo que realmente está ocurriendo, no quiero añadir aún más complejidad a lo que necesitas comprender para seguir mi razonamiento. Si quieres intentar identificar los fallos que aún existen y que se han manifestado durante la implementación de este código, aquí tienes una pista: uno de los fallos está relacionado con el número mínimo de ticks que debemos simular. Se trata de un fallo bastante interesante que hay que corregir. Aunque este problema solo surgió porque ahora queremos simular ticks cuando la cantidad de estos supera un valor dado. Piensa un poco y entenderás cómo ocurre. El otro fallo aparece cuando copiamos los valores simulados dentro del array de ticks. Cuando esto sucede, el sistema de creación de barras durante la reproducción se ve comprometido. En muchos casos, puede generar patrones extraños e ilógicos. Además, en ciertos momentos, los ticks simplemente desaparecen, lo que impide que el sistema de reproducción funcione con precisión y de manera correcta.
Si quieres intentar resolver estos fallos antes de leer el próximo artículo, ¡genial! Será un gran ejercicio para que entiendas cómo corregir, crear y razonar para desarrollar tus propias soluciones. De todos modos, en el próximo artículo mostraré cómo solucionar estos fallos. Será muy interesante. Así que nos vemos en el próximo artículo.
Video que muestra un fallo del sistema
Pronto mostraré cómo corregirlo, pero no será de forma inmediata, ya que no es un problema crítico. Se trata solo de un problema relacionado con la carga o descarga de los objetos creados. Aquí tienes una excelente oportunidad. Si realmente quieres demostrar que ya tienes un buen dominio de la programación, puedes intentar corregir el fallo por tu cuenta antes de que te muestre cómo hacerlo. No hace falta que me muestres tus resultados, pero inténtalo antes de leer mi solución. Así, sabrás en qué nivel de aprendizaje te encuentras.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12231





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso