English Русский Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición (Parte 63): Presionando play en el servicio (IV)

Desarrollo de un sistema de repetición (Parte 63): Presionando play en el servicio (IV)

MetaTrader 5Ejemplos | 11 diciembre 2024, 07:13
120 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 62): Presionando play en el servicio (III)", los ticks simulados se trataron como si fueran reales. Este problema no es, de hecho, nuestro principal quebradero de cabeza, pero puede dificultar el funcionamiento de la aplicación al intentar realizar una temporización adecuada, de modo que la barra de un minuto se construya completamente dentro de esa ventana de tiempo de un minuto.

A pesar del avance logrado en el artículo anterior, mencioné al final del mismo que surgirían algunos errores provocados por la implementación de la simulación sobre datos reales. Reitero que estos errores no existen en la simulación pura, pero sí aparecerán, con toda seguridad, al combinarse con datos reales. Sin embargo, no suponen un problema a la hora de ejecutar las pruebas y determinar si el sistema que se está creando es viable o no, tanto para su mantenimiento como para su mejora. A menudo, desarrollamos algo solo para probar su viabilidad. Si la idea que hay detrás de la implementación no resulta viable, simplemente la descartamos tras haber invertido poco tiempo en correcciones u otros ajustes.

En el mismo artículo informé de cuáles serían estos errores y de su origen. Pero como el artículo ya contenía mucha información que deberías comprender a fondo antes de avanzar, decidí finalizarlo en un punto concreto. Sin embargo, aquí corregiremos esos fallos que se generaron no debido a cambios en el código, sino porque estamos implementando algo que no sería realmente necesario si no existiera una sobrecarga en el sistema de temporización. Dicha sobrecarga aún no puede percibirse completamente debido a que estamos en una etapa inicial de todo lo que necesita ser implementado.

Dado que las fallas están correlacionadas de alguna manera, podemos comenzar con cualquiera de ellas. Pero, como soy meticuloso con algunos detalles, primero resolveremos el problema del valor mínimo de ticks que deberemos simular cuando realicemos esta simulación basada en datos reales.


¿Cuál es el mínimo de ticks que deben ser creados realmente?

Responder a esta pregunta no es tan simple como parece. Mientras intentas modificar esta aplicación que nos permite realizar un sistema de repetición o simulación, debes recordar siempre un punto importante. En el caso de la repetición, podemos resolver esta cuestión forzando a la clase de simulación a tener un número mínimo de ticks para poder utilizarlos. Recuerda: cuando usamos la repetición, esto indica que estamos trabajando con datos reales. Sin embargo, al simular los ticks para controlar la temporización dentro de la ventana de un minuto, estamos haciendo algo diferente, especialmente cuando la simulación se basa en los datos de rate obtenidos. Y es aquí donde pueden surgir problemas, ya que actualmente el usuario no puede ajustar el valor máximo de ticks que podrían utilizarse. Sin embargo, pretendo incluir esta opción en el archivo de configuración para que podamos controlar dicho valor. La razón es que, si la estación de trabajo del usuario puede procesar más ticks de los que permite la aplicación, el propio usuario podrá modificar el valor en el archivo de configuración para lograr una reproducción más cercana a lo que realmente ocurrió. Lo mismo ocurre si la estación de trabajo del usuario no es capaz de manejar una cantidad específica de ticks: simplemente podrá reducirla a un valor menor, lo que permitirá una operación más fluida y mucho menos estresante.

Existe otra razón que también dificulta responder esta cuestión con un número. Tiene que ver con la posibilidad de simular los datos fuera de la aplicación, guardarlos y utilizarlos como si fueran una base real. Este es el peor escenario para el sistema de repetición. Sin embargo, este tipo de situación aún no se abordará. Primero, resolveremos el problema de los ajustes incorrectos realizados por el usuario en la aplicación. Volvamos al punto inicial: ¿cuál es el mínimo de ticks que deberíamos tener? Depende. Para entenderlo, es necesario reflexionar un poco. Si el precio de apertura, cierre, máximo y mínimo son iguales, un tick es suficiente. Pero este es el caso más simple de todos. Para comprender los demás casos, debemos utilizar algunas definiciones. La primera es que la apertura y el cierre deben estar, obligatoriamente, dentro de los límites del máximo y el mínimo. A partir de esto, tenemos los siguientes escenarios:

  • si la apertura es igual a uno de los límites y el cierre es igual al otro límite, podemos hacerlo con dos ticks.
  • Si la apertura o el cierre es igual a uno de los límites y el límite opuesto no coincide con la apertura ni con el cierre, podemos hacerlo con tres ticks.
  • Si los cuatro valores que representan el OHLC son diferentes, o incluso si la apertura es igual al cierre, pero estos dos valores no se encuentran dentro de los límites de la barra, necesitaremos cuatro ticks.

Esta es la base para determinar cuántos ticks como mínimo necesitaremos para realizar la simulación. Hasta ahora, la simulación se ha ejecutado sin ningún problema. Sin embargo, quiero permitir que el usuario configure un límite para garantizar que su estación de trabajo pueda hacer que la aplicación responsable de la repetición o simulador funcione de manera adecuada.

Tal vez esta explicación haya resultado demasiado complicada en forma de texto. Pero en forma de código, todo se simplifica. Así que, para evitar tener que repetir todo el código del archivo C_Simulation.mqh, a continuación colocaré únicamente el fragmento que realmente fue necesario modificar para solucionar el problema existente.

128. //+------------------------------------------------------------------+
129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume)
130.          {
131.             int    i0, i1, i2, dm = 0;
132.             bool   b0;
133.             
134.             m_Marks.iMax   = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume));
135.             dm = (dm == 0 ? ((rate.open == rate.high == rate.low == rate.close) ? 1 : dm) : dm);
136.             dm = (dm == 0 ? (((rate.open == rate.high) && (rate.low == rate.close)) || ((rate.open == rate.low) && (rate.close == rate.high)) ? 2 : dm) : dm);
137.             if ((dm == 0 ? ((rate.open == rate.close == rate.high) || (rate.open == rate.close == rate.low) ? 3 : 4) : dm) == 0) return -1;            
138.             m_Marks.iMax   = (MaxTickVolume <= dm ? dm : MaxTickVolume);
139.             m_Marks.iMax   = (((int)rate.tick_volume > m_Marks.iMax) || ((int)rate.tick_volume < dm) ? m_Marks.iMax : (int)rate.tick_volume - 1);
140.             m_Marks.bHigh    = (rate.open == rate.high) || (rate.close == rate.high);
141.             m_Marks.bLow    = (rate.open == rate.low) || (rate.close == rate.low);
142.             Simulation_Time(rate, tick);
143.             MountPrice(0, rate.open, rate.spread, tick);
144.             if (m_Marks.iMax > 10)
145.             {
146.                i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2));
147.                i1 = m_Marks.iMax - i0;
148.                i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0);
149.                i2 = (i2 == 0 ? 1 : i2);
150.                b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low));
151.                i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2);
152.                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);
153.                RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2);
154.                m_Marks.bHigh = m_Marks.bLow = true;
155. 
156.             }else Random_Price(rate, tick);
157.             if (!m_IsPriceBID) DistributeVolumeReal(rate, tick);
158.             if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick);
159.             if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick);
160.             MountPrice(m_Marks.iMax, rate.close, rate.spread, tick);
161.             CorretTime(tick);
162. 
163.             return m_Marks.iMax;
164.          }
165. //+------------------------------------------------------------------+

Fragmento del archivo C_Simulation.mqh

La numeración de las líneas, visible en este fragmento, indica exactamente dónde deberás modificar el código que se encuentra completo en el artículo anterior. Observa que en la línea 131 he añadido una nueva variable que ya estoy inicializando directamente en su declaración. También debes eliminar la línea 134 original y añadir tres nuevas líneas en su lugar. Estas tres líneas cumplen con los requisitos que mencioné anteriormente sobre cuántos ticks deben crearse como mínimo. Sin embargo, hay un detalle importante que debes tener en cuenta. Observa que, durante la prueba con el operador ternario, la línea 138 compara el valor de MaxTickVolume con el valor que acabamos de ajustar para determinar el número mínimo de ticks. Si MaxTickVolume es menor que este valor ajustado, se usará el valor ajustado, con independencia de cualquier otra información. De hecho, en la línea 139 volvemos a verificar esta misma condición. De este modo, si el valor de rate.tick_volume también resulta ser menor que el valor ajustado, este tendrá prioridad.

Ahora, recuerda probar el retorno de esta función de simulación si planeas usarla para otro propósito. Si al intentar ajustar el valor mínimo de ticks se produce una falla, la función retornará directamente en la línea 137, por lo que no intentes usar los valores del array sin antes verificar el retorno de la función, ya que podrían contener datos inválidos.

A continuación, se muestra una pequeña modificación respecto al código del artículo anterior. Sin embargo, más adelante en este mismo artículo volveré a este fragmento para que la explicación de los cambios realizados sea más clara.

De este modo, hemos solucionado nuestro primer problema. Ahora pasemos al siguiente tema para resolver el segundo problema.


Resolvemos la falla en el ajuste de los ticks

Esta segunda avería es un poco más difícil de resolver. Sin embargo, el hecho de que sea más laboriosa no significa que sea más difícil, simplemente requiere un poco más de trabajo. Hagamos lo siguiente: revisemos el fragmento de código encargado de realizar los movimientos y ajustar los ticks para que puedan utilizarse en el trazado del gráfico, tal y como se explica en el artículo anterior. El fragmento en cuestión se muestra a continuación para facilitar su comprensión.

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 archivo C_FileTicks.mqh

No olvides que este fragmento contiene los errores que debemos resolver en este artículo. Muy bien, ahora presta mucha atención al fragmento siguiente y compáralo con el anterior.

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.             m_Ticks.bTickReal = true;
21.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
22.             {
23.                if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
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.                      ArrayResize(TicksLocal, def_MaxSizeArray);
31.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
32.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
33.                         nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
34.                      delete pSimulator;
35.                      ArrayFree(TicksLocal);
36.                      if (c1 < 0) return 0;
37.                   }
38.                   MemShift = nShift;
39.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
40.                };
41.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
42.             }
43.             if (!ToReplay)
44.             {
45.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
46.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
47.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
48.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
49.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
50.                m_Ticks.nTicks = MemNTicks;
51.                ArrayFree(RatesLocal);
52.             }else m_Ticks.nTicks = nShift;
53.                            
54.             return dtRet;
55.          };
56. //+------------------------------------------------------------------+

Fragmento del archivo C_FileTicks.mqh ( Final )

¿Ven las diferencias? No son muy evidentes, pero están ahí. Puedes notar que he cambiado el orden de ejecución de ciertos puntos. El cambio principal y más evidente es la asignación y liberación de memoria para los ticks creados durante la simulación. Dado que esta función, LoadTicks, es una función primaria —es decir, se ejecuta antes de que el sistema entre realmente en funcionamiento y exija rendimiento—, podemos darnos el lujo de perder algo de tiempo durante las llamadas para asignar o liberar memoria. Si consideras que perder tiempo de esta manera no es aceptable, siéntete libre de ajustar el orden en que suceden las cosas. Sin embargo, en este fragmento corregido que se muestra a continuación, no debemos omitir la llamada al destructor de la clase de simulación en caso de que se produzca una falla. Si comparas los fragmentos, notarás que el retorno solo ocurre en caso de fallo, en la línea 36. Pero antes de eso, en las líneas 34 y 35, llamamos al destructor y liberamos la memoria asignada. En este orden.

Si la simulación se realiza correctamente y los datos pueden moverse, lo hacemos en la línea 33, donde también utilizamos el retorno de la función de biblioteca para actualizar el nuevo valor de desplazamiento.

Con esto, resolvemos el problema que se producía cuando la simulación se consideraba fallida. Sin embargo, aún necesito explicar otro error que también existía aquí. Si observas el fragmento corregido, te darás cuenta de algo curioso. Quizás, y muy probablemente, esto no tenga mucho sentido para ti a primera vista; se puede ver en la línea 23. ¿Por qué se ha incluido la línea 23 en este fragmento? ¿Qué sentido tiene comparar el valor del contador de ticks con el desplazamiento? Bueno, no tiene mucho sentido, es cierto. Pero, sin embargo, no obstante. Si el simulador se ejecuta, el valor de desplazamiento será diferente al valor del contador. Cuando esto ocurre, algunos datos de ticks reales quedan en el índice equivocado. Si no se corrige, al ejecutar la línea 52, muchos ticks reales podrían simplemente desaparecer. Y no solo eso, sino que también aparecerán ticks extraños entre una barra simulada y una no simulada, ya que el índice de estos ticks estará completamente erróneo. Ahora creo que has logrado entender el verdadero problema. Por tanto, si realizamos la prueba en la línea 23 para verificar y mover los ticks reales, cuando todo se ponga en marcha no habrá cosas extrañas apareciendo en el gráfico. Es una medida simple, pero que resuelve definitivamente nuestro problema. Muy bien, así que el código final que se encuentra en el archivo C_FileTicks.mqh puede verse completo a continuación:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_FileBars.mqh"
005. #include "C_Simulation.mqh"
006. //+------------------------------------------------------------------+
007. #define macroRemoveSec(A)   (A - (A % 60))
008. #define def_MaxSizeArray    16777216 // 16 Mbytes
009. //+------------------------------------------------------------------+
010. class C_FileTicks
011. {
012.    protected:
013.       enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
014.       struct stInfoTicks
015.       {
016.          MqlTick     Info[];
017.          MqlRates    Rate[];
018.          int         nTicks,
019.                      nRate;
020.          bool        bTickReal;
021.          ePlotType   ModePlot;
022.       };
023. //+------------------------------------------------------------------+
024. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew)
025.          {
026.             double    dClose = 0;
027.             
028.             switch (m_Ticks.ModePlot)
029.             {
030.                case PRICE_EXCHANGE:
031.                   if (m_Ticks.Info[iArg].last == 0.0) return false;
032.                   dClose = m_Ticks.Info[iArg].last;
033.                   break;
034.                case PRICE_FOREX:
035.                   dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose);
036.                   if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false;
037.                   break;
038.             }
039.             if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time)))
040.             {
041.                rate.time = macroRemoveSec(m_Ticks.Info[iArg].time);
042.                rate.real_volume = 0;
043.                rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0);
044.                rate.open = rate.low = rate.high = rate.close = dClose;
045.             }else
046.             {
047.                rate.close = dClose;
048.                rate.high = (rate.close > rate.high ? rate.close : rate.high);
049.                rate.low = (rate.close < rate.low ? rate.close : rate.low);
050.                rate.real_volume += (long) m_Ticks.Info[iArg].volume_real;
051.                rate.tick_volume += (m_Ticks.bTickReal ? 1 : (int)m_Ticks.Info[iArg].volume);
052.             }
053.             
054.             return true;         
055.          }
056. //+------------------------------------------------------------------+
057.    private   :
058.       int         m_File;
059.       stInfoTicks m_Ticks;
060. //+------------------------------------------------------------------+
061. inline bool Open(const string szFileNameCSV)
062.          {
063.             string szInfo = "";
064.             
065.             if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
066.             {
067.                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File);
068.                if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true;
069.                Print("File ", szFileNameCSV, ".csv not a traded tick file.");
070.             }else
071.                Print("Tick file ", szFileNameCSV,".csv not found...");
072.                
073.             return false;
074.          }
075. //+------------------------------------------------------------------+
076. inline bool ReadAllsTicks(void)
077.          {
078.             string    szInfo;
079.                         
080.             Print("Loading replay ticks. Please wait...");
081.             ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
082.             m_Ticks.ModePlot = PRICE_FOREX;
083.             while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
084.             {
085.                ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
086.                szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
087.                m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19));
088.                m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3));
089.                m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File));
090.                m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File));
091.                m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File));
092.                m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File));
093.                m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File));
094.                m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
095.                m_Ticks.nTicks++;
096.             }
097.             FileClose(m_File);
098.             if (m_Ticks.nTicks == (INT_MAX - 2))
099.             {
100.                Print("Too much data in tick file.\nIt is not possible to continue...");
101.                return false;
102.             }
103.             return (!_StopFlag);
104.          }
105. //+------------------------------------------------------------------+
106.       int SetSymbolInfos(void)
107.          {
108.             int iRet;
109.             
110.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5));
111.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
112.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
113.             
114.             return iRet;
115.          }
116. //+------------------------------------------------------------------+
117.    public   :
118. //+------------------------------------------------------------------+
119.       C_FileTicks()
120.          {
121.             ArrayResize(m_Ticks.Rate, def_BarsDiary);
122.             m_Ticks.nRate = -1;
123.             m_Ticks.nTicks = 0;
124.             m_Ticks.Rate[0].time = 0;
125.          }
126. //+------------------------------------------------------------------+
127.       bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume)
128.          {
129.             C_FileBars    *pFileBars;
130.             C_Simulation  *pSimulator = NULL;
131.             int           iMem = m_Ticks.nTicks,
132.                           iRet = -1;
133.             MqlRates      rate[1];
134.             MqlTick       local[];
135.             bool          bInit = false;
136.             
137.             pFileBars = new C_FileBars(szFileNameCSV);
138.             ArrayResize(local, def_MaxSizeArray);
139.             Print("Converting bars to ticks. Please wait...");
140.             while ((*pFileBars).ReadBar(rate) && (!_StopFlag))
141.             {
142.                if (!bInit)
143.                {
144.                   m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX);
145.                   pSimulator = new C_Simulation(SetSymbolInfos());
146.                   bInit = true;
147.                }
148.                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
149.                m_Ticks.Rate[++m_Ticks.nRate] = rate[0];
150.                if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local, MaxTickVolume);
151.                if (iRet < 0) break;
152.                for (int c0 = 0; c0 <= iRet; c0++)
153.                {
154.                   ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
155.                   m_Ticks.Info[m_Ticks.nTicks++] = local[c0];
156.                }
157.             }
158.             ArrayFree(local);
159.             delete pFileBars;
160.             delete pSimulator;
161.             m_Ticks.bTickReal = false;
162.             
163.             return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0));
164.          }
165. //+------------------------------------------------------------------+
166.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
167.          {
168.             int      MemNRates,
169.                      MemNTicks,
170.                      nDigits,
171.                      nShift;
172.             datetime dtRet = TimeCurrent();
173.             MqlRates RatesLocal[],
174.                      rate;
175.             MqlTick  TicksLocal[];
176.             bool     bNew;
177.             
178.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
179.             nShift = MemNTicks = m_Ticks.nTicks;
180.             if (!Open(szFileNameCSV)) return 0;
181.             if (!ReadAllsTicks()) return 0;         
182.             rate.time = 0;
183.             nDigits = SetSymbolInfos(); 
184.             m_Ticks.bTickReal = true;
185.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
186.             {
187.                if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
188.                if (!BuildBar1Min(c0, rate, bNew)) continue;
189.                if (bNew)
190.                {
191.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
192.                   {
193.                      nShift = MemShift;
194.                      ArrayResize(TicksLocal, def_MaxSizeArray);
195.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
196.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
197.                         nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
198.                      delete pSimulator;
199.                      ArrayFree(TicksLocal);
200.                      if (c1 < 0) return 0;
201.                   }
202.                   MemShift = nShift;
203.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
204.                };
205.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
206.             }
207.             if (!ToReplay)
208.             {
209.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
210.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
211.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
212.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
213.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
214.                m_Ticks.nTicks = MemNTicks;
215.                ArrayFree(RatesLocal);
216.             }else m_Ticks.nTicks = nShift;
217.                            
218.             return dtRet;
219.          };
220. //+------------------------------------------------------------------+
221. inline stInfoTicks GetInfoTicks(void) const
222.          {
223.             return m_Ticks;
224.          }
225. //+------------------------------------------------------------------+
226. };
227. //+------------------------------------------------------------------+
228. #undef def_MaxSizeArray
229. //+------------------------------------------------------------------+

Archivo de cabecera C_FileTicks.mqh

Bien, ahora que hemos resuelto estos problemas, podemos pasar al siguiente paso. Este será permitir que el usuario defina un valor que se usará como máximo de ticks en la barra de un minuto. Pero, para separar los temas, lo trataremos en un nuevo apartado.


Permitimos al usuario ajustar las cosas

Esta parte es, sin lugar a dudas, la más fácil, simple y divertida. Esto se debe a que el único trabajo que tendrás como programador será definir el nombre de la clave que se usará para establecer el valor que necesitamos ajustar. Esta clave es el valor que el usuario deberá introducir en el archivo de configuración del símbolo. Ya he mostrado anteriormente en esta misma serie cómo realizar esta tarea. Sin embargo, como es algo extremadamente simple, no fragmentaré el código para explicar qué se añadirá. Pero antes de mostrar el código final, veamos primero un ejemplo de uso donde podemos observar un archivo de configuración de la aplicación la repetición/simulador. Este ejemplo se encuentra justo a continuación.

01. [Config]
02. Path = WDO
03. PointsPerTick = 0.5
04. ValuePerPoints = 5.0
05. VolumeMinimal = 1.0
06. Account = NETTING
07. MaxTicksPerBar = 2800
08. 
09. [Bars]
10. WDON22_M1_202206140900_202206141759
11. 
12. [ Ticks -> Bars]
13. 
14. [ Bars -> Ticks ]
15. 
16. [Ticks]
17. WDON22_202206150900_202206151759

Ejemplo de archivo de configuración

Observa que en la línea siete se está realizando una nueva configuración. Si utilizas este archivo de configuración en la versión anterior de la aplicación que realiza la repetición/simulación en MetaTrader 5, recibirás un mensaje de error que indica que la línea siete contiene algo que el sistema no puede comprender. Sin embargo, a partir de esta versión, la aplicación podrá entender lo que significa la línea siete. Ahora, un detalle: tú o incluso el usuario no estáis obligados a incluir este dato configurado en la línea siete. Si decides hacerlo, esta configuración se impondrá a la que existe internamente en la aplicación ya compilada. Pero si ignoras esta configuración y no la defines, el servicio que ejecuta la repetición/simulación utilizará el valor interno definido en el momento de la compilación de la aplicación.

Lo menciono antes de mostrar el código porque quiero que prestes atención al hecho de que esta configuración es opcional. Sin embargo, se impondrá al valor definido durante el proceso de compilación. Un último detalle que no debes olvidar es que cada archivo de configuración es único. Es decir, puedes definir una cantidad máxima de ticks diferente para cada archivo. Así que siéntete libre de probar el sistema hasta encontrar una configuración que no afecte al rendimiento de MetaTrader 5, de modo que el gráfico se trace de la forma más fluida posible.

Ahora bien, veamos el código del archivo de cabecera responsable de ejecutar lo indicado en este archivo de configuración. El archivo en cuestión es C_ConfigService.mqh, cuyo nuevo código se muestra a continuación completo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Support\C_FileBars.mqh"
005. #include "Support\C_FileTicks.mqh"
006. #include "Support\C_Array.mqh"
007. //+------------------------------------------------------------------+
008. class C_ConfigService : protected C_FileTicks
009. {
010.    protected:
011. //+------------------------------------------------------------------+
012.    private   :
013.       enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
014.       enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
015.       struct st001
016.       {
017.          C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
018.          int      Line,
019.                   MaxTickVolume;
020.          bool     AccountHedging;
021.          char     ModelLoading;
022.          string   szPath;
023.       }m_GlPrivate;
024. //+------------------------------------------------------------------+
025. inline void FirstBarNULL(void)
026.          {
027.             MqlRates rate[1];
028.             int c0 = 0;
029.             
030.             for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++);
031.             rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid);
032.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
033.             rate[0].tick_volume = 0;
034.             rate[0].real_volume = 0;
035.             rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400;
036.             CustomRatesUpdate(def_SymbolReplay, rate);
037.          }
038. //+------------------------------------------------------------------+
039. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
040.          {
041.             string szInfo;
042.             
043.             szInfo = In;
044.             Out = "";
045.             StringToUpper(szInfo);
046.             StringTrimLeft(szInfo);
047.             StringTrimRight(szInfo);
048.             if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
049.             if (StringSubstr(szInfo, 0, 1) != "[")
050.             {
051.                Out = szInfo;
052.                return Transcription_INFO;
053.             }
054.             for (int c0 = 0; c0 < StringLen(szInfo); c0++)
055.                if (StringGetCharacter(szInfo, c0) > ' ')
056.                   StringAdd(Out, StringSubstr(szInfo, c0, 1));               
057.             
058.             return Transcription_DEFINE;
059.          }
060. //+------------------------------------------------------------------+
061. inline bool Configs(const string szInfo)
062.          {
063.             const string szList[] = {
064.                      "PATH",
065.                      "POINTSPERTICK",
066.                      "VALUEPERPOINTS",
067.                      "VOLUMEMINIMAL",
068.                      "LOADMODEL",
069.                      "ACCOUNT",
070.                      "MAXTICKSPERBAR"
071.                                     };
072.             string    szRet[];
073.             char      cWho;
074.             
075.             if (StringSplit(szInfo, '=', szRet) == 2)
076.             {
077.                StringTrimRight(szRet[0]);
078.                StringTrimLeft(szRet[1]);
079.                for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
080.                switch (cWho)
081.                {
082.                   case 0:
083.                      m_GlPrivate.szPath = szRet[1];
084.                      return true;
085.                   case 1:
086.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
087.                      return true;
088.                   case 2:
089.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
090.                      return true;
091.                   case 3:
092.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
093.                      return true;
094.                   case 4:
095.                      m_GlPrivate.ModelLoading = StringInit(szRet[1]);
096.                      m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading);
097.                      return true;
098.                   case 5:
099.                      if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true;
100.                      else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false;
101.                      else
102.                      {
103.                         Print("Entered account type is not invalid.");                        
104.                         return false;
105.                      }
106.                      return true;
107.                   case 6:
108.                      m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1]));
109.                      return true;         
110.                }
111.                Print("Variable >>", szRet[0], "<< not defined.");
112.             }else
113.                Print("Configuration definition >>", szInfo, "<< invalidates.");
114.                
115.             return false;
116.          }
117. //+------------------------------------------------------------------+
118. inline bool WhatDefine(const string szArg, char &cStage)
119.          {
120.             const string szList[] = {
121.                      "[BARS]",
122.                      "[TICKS]",
123.                      "[TICKS->BARS]",
124.                      "[BARS->TICKS]",
125.                      "[CONFIG]"
126.                                     };
127.                                     
128.             cStage = 1;
129.             for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++)
130.                if (szList[c0] == szArg) return true;
131.                
132.             return false;
133.          }
134. //+------------------------------------------------------------------+
135. inline bool CMD_Array(char &cError, eWhatExec e1)
136.          {
137.             bool       bBarsPrev = false;
138.             string     szInfo;
139.             C_FileBars *pFileBars;
140.             C_Array    *ptr = NULL;
141.             
142.             switch (e1)
143.             {
144.                case eTickReplay : ptr = m_GlPrivate.pTicksToReplay; break;
145.                case eTickToBar  : ptr = m_GlPrivate.pTicksToBars;   break;
146.                case eBarToTick  : ptr = m_GlPrivate.pBarsToTicks;   break;
147.                case eBarPrev    : ptr = m_GlPrivate.pBarsToPrev;    break;
148.             }            
149.             if (ptr != NULL)
150.             {
151.                for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++)
152.                {
153.                   if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break;
154.                   switch (e1)
155.                   {
156.                      case eTickReplay:
157.                         if (LoadTicks(szInfo, true, m_GlPrivate.MaxTickVolume) == 0) cError = 4;
158.                         break;
159.                      case eTickToBar :
160.                         if (LoadTicks(szInfo, false, m_GlPrivate.MaxTickVolume) == 0) cError = 5; else bBarsPrev = true;
161.                         break;
162.                      case eBarToTick :
163.                         if (!BarsToTicks(szInfo, m_GlPrivate.MaxTickVolume)) cError = 6;
164.                         break;
165.                      case eBarPrev   :
166.                         pFileBars = new C_FileBars(szInfo);
167.                         if ((*pFileBars).LoadPreView() == 0) cError = 3; else bBarsPrev = true;
168.                         delete pFileBars;
169.                         break;
170.                   }
171.                }
172.                delete ptr;
173.             }
174.             
175.             return bBarsPrev;
176.          }
177. //+------------------------------------------------------------------+
178.    public   :
179. //+------------------------------------------------------------------+
180.       C_ConfigService()
181.          :C_FileTicks()
182.          {
183.             m_GlPrivate.AccountHedging = false;
184.             m_GlPrivate.ModelLoading = 1;
185.             m_GlPrivate.MaxTickVolume = 2000;
186.          }
187. //+------------------------------------------------------------------+
188. inline const bool TypeAccountIsHedging(void) const
189.          {
190.             return m_GlPrivate.AccountHedging;
191.          }
192. //+------------------------------------------------------------------+
193.       bool SetSymbolReplay(const string szFileConfig)
194.          {
195. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo)
196.             int      file;
197.             char     cError,
198.                      cStage;
199.             string   szInfo;
200.             bool     bBarsPrev;
201.                         
202.             if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
203.             {
204.                Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated...");
205.                return false;
206.             }
207.             Print("Loading data for playback. Please wait....");
208.             cError = cStage = 0;
209.             bBarsPrev = false;
210.             m_GlPrivate.Line = 1;
211.             m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL;
212.             while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
213.             {
214.                switch (GetDefinition(FileReadString(file), szInfo))
215.                {
216.                   case Transcription_DEFINE:
217.                      cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
218.                      break;
219.                   case Transcription_INFO:
220.                      if (szInfo != "") switch (cStage)
221.                      {
222.                         case 0:
223.                            cError = 2;
224.                            break;
225.                         case 1:
226.                            if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array();
227.                            (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line);
228.                            break;
229.                         case 2:
230.                            if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array();
231.                            (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line);
232.                            break;
233.                         case 3:
234.                            if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array();
235.                            (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line);
236.                            break;
237.                         case 4:
238.                            if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array();
239.                            (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line);
240.                            break;
241.                         case 5:
242.                            if (!Configs(szInfo)) cError = 7;
243.                            break;
244.                      }
245.                      break;
246.                };
247.                m_GlPrivate.Line += (cError > 0 ? 0 : 1);
248.             }
249.             FileClose(file);
250.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick));
251.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay));
252.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev);
253.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev);
254.             switch(cError)
255.             {
256.                case 0:
257.                   if (GetInfoTicks().nTicks <= 0)
258.                   {
259.                      Print("There are no ticks to use. Service is being terminated...");
260.                      cError = -1;
261.                   }else if (!bBarsPrev) FirstBarNULL();
262.                   break;
263.                case 1  : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system..."); break;
264.                case 2  : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line);         break;
265.                default : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line);
266.             }
267.                      
268.             return (cError == 0 ? !_StopFlag : false);
269. #undef macroFileName
270.          }
271. //+------------------------------------------------------------------+
272. };
273. //+------------------------------------------------------------------+

Código fuente del archivo de cabecera C_ConfigService.mqh

Muy bien. El código de esta clase, C_ConfigService, es muy fácil de trabajar. Esto se debe a que casi no necesita modificaciones y podemos realizar cualquier tipo de ajuste con muy poco esfuerzo. Es raro encontrar algo tan versátil. Pero vayamos al grano, porque aquí es donde hacemos que todas las capas inferiores trabajen de manera intensiva para permitirnos cargar los datos que se utilizarán para realizar la reproducción o la simulación. Y todo esto se gestiona según lo indicado en el contenido del archivo de configuración, como se muestra en el ejemplo anterior.

Pues bien, lo primero que hicimos en este código fue definir una nueva variable. Puede observarse en la línea 19 y su nombre es bastante sugerente, ya que nos muestra claramente lo que haremos en los próximos pasos. Dicha variable se inicializa en la línea 185, dentro del constructor de la clase. Debido a esta inicialización, si en el archivo de configuración no se define otro valor, el valor que se utilizará será exactamente el que aparece aquí.

Ahora puedes estar preguntándote: "pero, espera un momento, ¿por qué no utilizas la definición def_MaxTicksVolume que existe en el archivo Defines.mqh?" El motivo es que dicha definición ya no existe. Fue eliminada porque ya no la necesitábamos. Desde que empezamos a trabajar con el archivo de configuración, dejamos de depender de ciertas cosas. Por eso se eliminó la definición def_MaxTicksVolume del archivo Defines.mqh. Si decides mantenerla, no pasa nada. Pero no te sorprendas si en el futuro, al revisar el archivo Defines.mqh en algún artículo, no encuentras la definición def_MaxTicksVolume.

Ahora veremos por qué esa definición dejó de existir y ya no es necesaria. Durante la implementación del sistema que permitía la simulación de ticks en caso de que se detectara un exceso de ellos en la barra de un minuto, inicialmente no se había definido dónde se proporcionaría esa información. Para evitar la mezcla de valores en el código, decidí crear una definición general. Así surgió def_MaxTicksVolume, que quedó en el archivo Defines.mqh. Tomar este tipo de decisión es algo común cuando se diseña algo que se sabe que cambiará con el tiempo. Pero, cuando llegamos a la clase C_ConfigService, se me ocurrió permitir que el usuario pudiera ajustar ese mismo valor, por lo que ya no sería necesario recompilar todo el código.

De todas formas, las clases que realizaban el trabajo más pesado ya estaban creadas. Por lo tanto, lo único necesario era pasarles el valor del número máximo de ticks. Así, con una simple modificación del código, logramos que las llamadas incluyan ese valor, que se puede obtener fácilmente desde la clase de configuración del servicio. Dichas llamadas están presentes en las líneas 157, 160 y 163. Hasta ahora, no se ha realizado ninguna modificación importante en el código, solo una ligera edición para agregar el soporte necesario. Esto permite que las llamadas de las clases inferiores reciban de manera controlada un valor definido para toda la aplicación, con el fin de controlar el número máximo de ticks que se simularán o que deberán existir dentro de una barra de un minuto.

Ahora llegamos al verdadero motivo por el que las cosas se dirigieron hacia esta clase. Permitir que el usuario defina el valor máximo de ticks que aparecerán en la barra de un minuto. Si estás viendo este código por primera vez, podrías pensar que hacer esto supondrá una tarea enorme y extremadamente compleja. Pero no es así. Observa lo sencillo, fácil y rápido que es permitir que el usuario defina un valor que se utilizará como número máximo de ticks. Primero, añadimos la clave que se utilizará. Esto se hace añadiendo una nueva cadena a la lista de cadenas que sirven como claves para los parámetros de configuración. Esta matriz se define en la línea 63. Ahora presta atención, porque ya he explicado esto antes, pero lo repetiré para que quede claro. Para añadir una nueva clave, esta debe definirse completamente en mayúsculas. Da igual cuál sea la clave, siempre debe definirse en mayúsculas. En nuestro caso, la nueva clave se encuentra en la línea 70. Ahora hay un segundo truco: fíjate en que en la línea 75 utilizamos una función de la biblioteca MQL5 para separar los valores. El delimitador que se usa es el signo igual. Entonces, lo que está antes del signo igual se considera la clave de la definición y lo que está después del signo es el valor que se asignará a dicha definición.

El siguiente punto de atención está en la línea 79, donde se busca la posición de la clave en la matriz de definiciones. Por tanto, si modificas la matriz, también deberás modificar el valor que será el índice de la configuración. No es complicado, solo tienes que prestar atención a lo que estás haciendo.

En el caso de nuestra nueva definición, su índice es el seis. Por lo tanto, en la línea 107 definimos cómo se aplicará correctamente. Dado que el valor que esperamos es entero, utilizamos otra función de la biblioteca MQL5 para convertirlo. De esta manera, el usuario puede definir un valor que se utilizará como límite máximo de ticks en una barra.

Observación importante: aquí, en la línea 108, donde convertimos el valor presente en el archivo de configuración de la repetición/simulación para usarlo como número máximo de ticks, no realizamos ningún tipo de prueba para verificar si el valor se ajusta a los parámetros esperados. Lo único que hacemos es garantizar que el valor sea siempre positivo. Sin embargo, si hay alguna inconsistencia, podría hacer que el proceso de simulación fallara.


Consideraciones finales

Antes de terminar este artículo, me gustaría recordar un punto mencionado en el artículo anterior. Se trata del video que puedes encontrar en él. Aunque el enfoque principal de este artículo ha sido la implementación del sistema de límite de ticks en el caso de usar la repetición, quiero destacar que no se han olvidado las fallas mostradas en el video del artículo anterior. Aunque no representan un problema grave que haga que toda la aplicación sea inestable o que afecte a la plataforma, no se ha constatado que realmente afecten al funcionamiento general. Se trata más bien de un inconveniente al eliminar ciertos objetos gráficos al cerrar el gráfico. Aunque esta falla ocurre en situaciones muy específicas, estamos trabajando en la solución adecuada. Tan pronto como esto se solucione, el artículo en el que se explican los pasos seguidos para solucionar esta falla estará a tu disposición, estimado lector que has seguido esta secuencia.

Para concluir, te invito a ver el video de abajo. En él se muestra cómo funciona el sistema actualmente y, especialmente, su comportamiento cuando se utiliza o no el control del número de ticks definido en el archivo de configuración.

El video es muy corto y permite observar de forma simplificada la principal diferencia entre usar datos simulados y reales. Verás que, al usar la simulación mediante Random Walk para llenar los ticks, el movimiento de estos es significativamente diferente del movimiento real producido por las transacciones ejecutadas.

Sin embargo, quiero señalar otra deficiencia. Aunque es relativamente molesta y ocurre con frecuencia, no tiene una gran repercusión. Simplemente resulta incómoda. Puedes identificar esta falla en el video. Así que, si compilas el código actual y lo utilizas, debes tener en cuenta que, de vez en cuando, el servicio de repetición/simulador entrará automáticamente en un estado de pausa. Será necesario volver a activarlo manualmente. Esta falla, aunque molesta, debe corregirse cuanto antes. Por lo tanto, en el próximo artículo abordaremos las correcciones necesarias para resolver este problema y evitar que el servicio de repetición/simulación entre en pausa automáticamente. Además, también proporcionaremos información sobre el retorno de ciertas funciones que aún se encuentran deshabilitadas en el servicio de repetición/simulador.

Video de demostración

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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Desarrollo de un sistema de repetición (Parte 64): Presionando play en el servicio (V) Desarrollo de un sistema de repetición (Parte 64): Presionando play en el servicio (V)
En este artículo, mostraré cómo corregir dos errores presentes en el código. Sin embargo, he intentado explicarlas de manera que tú, aspirante a programador, entiendas que las cosas no siempre ocurrirán como habías previsto. Pero esto no debe ser motivo de desesperación, sino una oportunidad para aprender. El contenido expuesto aquí tiene como único propósito ser didáctico. En ningún caso debe interpretarse como una aplicación cuya finalidad sea distinta al aprendizaje y estudio de los conceptos presentados.
Modificaciones más notables del algoritmo de búsqueda cooperativa artificial (Artificial Cooperative Search, ACSm) Modificaciones más notables del algoritmo de búsqueda cooperativa artificial (Artificial Cooperative Search, ACSm)
Aquí consideraremos la evolución del algoritmo ACS: tres modificaciones destinadas a mejorar las características de convergencia y la eficiencia del algoritmo. Transformación de uno de los principales algoritmos de optimización. De las modificaciones matriciales a los planteamientos revolucionarios en materia de formación de la población.
Desarrollo de un sistema de repetición (Parte 65): Presionando play en el servicio (VI) Desarrollo de un sistema de repetición (Parte 65): Presionando play en el servicio (VI)
En este artículo, mostraré cómo lo implementaremos y resolveremos el problema del indicador del mouse cuando se utiliza junto con la aplicación de repetición/simulación. El contenido expuesto aquí tiene como único propósito la enseñanza. En ningún caso debe considerarse una aplicación cuya finalidad no sea el aprendizaje y estudio de los conceptos presentados.
Desarrollo de un sistema de repetición (Parte 62): Presionando play en el servicio (III) Desarrollo de un sistema de repetición (Parte 62): Presionando play en el servicio (III)
En este artículo comenzaremos a abordar el problema del exceso de ticks, que puede afectar a la aplicación cuando usamos datos reales. Este exceso complica muchas veces la correcta temporización necesaria para construir la barra de un minuto dentro de la ventana adecuada.