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

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

MetaTrader 5Ejemplos |
125 1
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Desarrollo de un sistema de repetición (Parte 59): Un nuevo futuro, mostré y expliqué algunos de los cambios que se produjeron en los módulos de control de indicadores y del mouse. Aunque esos cambios nos brindan diversas posibilidades de uso futuro para el módulo de indicador de mouse, este aún contiene un pequeño error. Sin embargo, dicho fallo no afecta nuestro trabajo en este momento. Esto se debe a que, en este preciso instante, nuestro enfoque será resolver algunos problemas relacionados con el encapsulamiento presente en el código de las clases del sistema de repetición/simulador.

A pesar de que ya hemos realizado algunos cambios, como se mostró en artículos anteriores, estos no han sido suficientes para resolver completamente el problema del encapsulamiento. Todavía hay elementos que se filtran desde dentro de las clases, y debemos corregir esta situación de inmediato, ya que de lo contrario nos enfrentaremos a serios problemas dentro de poco. Además de este problema de encapsulamiento, que permite que ciertos elementos que no deberían filtrarse sean accesibles directamente fuera de la clase, hay otros aspectos que deben remodelarse para acceder de manera más adecuada a algunas variables e informaciones. Todo esto se abordará en este artículo.


Comenzamos a ajustar las cosas

Muy bien. El primer punto que abordaremos será resolver el problema de encapsulamiento de la clase C_FilesTicks. Esta clase se encuentra en el archivo C_FilesTicks.mqh, y contiene una fuga de información que no debería ser accesible, al menos no de la forma en que se está haciendo actualmente.

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

Código fuente del archivo C_FilesTicks.mqh

Es muy probable que no notes una gran diferencia, excepto, por supuesto, en la línea 21, donde hay algo destacado.

Lo que aparece destacado es la fuga que estaba ocurriendo. Es decir, aunque todo parecía estar seguro y funcionando perfectamente, si necesitábamos realizar alguna acción relacionada con los ticks cargados o simulados, habría un grave fallo de seguridad. El motivo es que cualquier clase podría modificar los datos de dicha estructura. Observa que, fuera del ámbito de las clases, dicho cambio no podría realizarse, ya que la estructura está en una cláusula protected. En un artículo anterior expliqué cómo influyen estas cláusulas en el nivel de acceso a los elementos dentro de una clase.

Pero el hecho de eliminar la variable y permitir únicamente el uso de la estructura ya nos proporciona un nivel mucho más aceptable de seguridad y encapsulamiento. Sin embargo, al realizar este cambio, debemos tomar otra decisión. Esa decisión es declarar una nueva variable, pero esta vez como privada, con el objetivo de mantener la estructura y añadir una función que permita acceder a dicha variable. O bien, declarar esa misma variable en otro lugar y pasarla como argumento a las funciones que necesiten acceder al contenido que contiene.

Puede parecer extraño, pero cada caso es único y tiene sus propias consecuencias y posibilidades. No obstante, debido a la naturaleza del proyecto, tomé la decisión de seguir el primer enfoque. Es decir, crearemos una variable privada, que puede verse en la línea 58. Nota que, fuera de este cambio, no fue necesario modificar nada más en el código. Sin embargo, al retirar el acceso a esta variable desde otras clases —y sí, estábamos usándola en otros lugares, no solo accediendo a los datos, sino también modificándolos, como se verá más adelante—, será necesario inicializar correctamente la estructura. Por esta razón, en la línea 118 aparece un constructor. Observa que este es bastante simple, realizando únicamente una tarea que antes se ejecutaba en otro lugar. Esto se debía a una fuga derivada de la falla de encapsulamiento que acabamos de corregir en la clase C_FilesTicks.

Además, como se mencionó anteriormente, es necesario crear un medio para acceder a la variable y a los datos que contiene. Esto se logra mediante la función ubicada en la línea 199. Presta mucha atención: podremos leer los datos dentro de la estructura, pero no podremos escribir en ella. Esto se debe a que el valor devuelto está declarado como constante. El uso de este tipo de implementación ya se explicó en algunos de mis artículos anteriores de esta misma serie; si tienes alguna duda, consulta dichos artículos.

Ahora que hemos resuelto uno de los problemas, debemos abordar otro que, en cierto modo, estaba relacionado con el que acabamos de solucionar. Este otro problema se encuentra en la clase C_ConfigService, ubicada en el archivo C_ConfigService.mqh. El código de este archivo de cabecera ha tenido que modificarse. Estos cambios se deben, en primer lugar, a que ahora ya no existe la fuga de datos de la clase C_FilesTicks. Además, algunos elementos declarados en esta clase se eliminarán, ya que no son necesarios allí, sino en otro lugar.

Por lo tanto, el nuevo código del archivo C_ConfigService.mqh puede verse a continuación:

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.          bool     AccountHedging;
020.          char     ModelLoading;
021.          string   szPath;
022.       }m_GlPrivate;
023.       string    m_szPath;
024.       bool      m_AccountHedging;
025.       datetime m_dtPrevLoading;
026.       int      m_ReplayCount,
027.                m_ModelLoading;
028. //+------------------------------------------------------------------+
029. inline void FirstBarNULL(void)
030.          {
031.             MqlRates rate[1];
032.             int c0 = 0;
033.             
034.             for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++);
035.             rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid);
036.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
037.             rate[0].tick_volume = 0;
038.             rate[0].real_volume = 0;
039.             rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400;
040.             CustomRatesUpdate(def_SymbolReplay, rate);
041.          }
042. //+------------------------------------------------------------------+
043. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
044.          {
045.             string szInfo;
046.             
047.             szInfo = In;
048.             Out = "";
049.             StringToUpper(szInfo);
050.             StringTrimLeft(szInfo);
051.             StringTrimRight(szInfo);
052.             if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
053.             if (StringSubstr(szInfo, 0, 1) != "[")
054.             {
055.                Out = szInfo;
056.                return Transcription_INFO;
057.             }
058.             for (int c0 = 0; c0 < StringLen(szInfo); c0++)
059.                if (StringGetCharacter(szInfo, c0) > ' ')
060.                   StringAdd(Out, StringSubstr(szInfo, c0, 1));               
061.             
062.             return Transcription_DEFINE;
063.          }
064. //+------------------------------------------------------------------+
065. inline bool Configs(const string szInfo)
066.          {
067.             const string szList[] = {
068.                      "PATH",
069.                      "POINTSPERTICK",
070.                      "VALUEPERPOINTS",
071.                      "VOLUMEMINIMAL",
072.                      "LOADMODEL",
073.                      "ACCOUNT"
074.                                     };
075.             string    szRet[];
076.             char      cWho;
077.             
078.             if (StringSplit(szInfo, '=', szRet) == 2)
079.             {
080.                StringTrimRight(szRet[0]);
081.                StringTrimLeft(szRet[1]);
082.                for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
083.                switch (cWho)
084.                {
085.                   case 0:
086.                      m_GlPrivate.szPath = szRet[1];
087.                      return true;
088.                   case 1:
089.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
090.                      return true;
091.                   case 2:
092.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
093.                      return true;
094.                   case 3:
095.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
096.                      return true;
097.                   case 4:
098.                      m_GlPrivate.ModelLoading = StringInit(szRet[1]);
099.                      m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading);
100.                      return true;
101.                   case 5:
102.                      if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true;
103.                      else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false;
104.                      else
105.                      {
106.                         Print("Entered account type is not invalid.");                        
107.                         return false;
108.                      }
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) == 0) cError = 4;
158.                         break;
159.                      case eTickToBar   :
160.                         if (LoadTicks(szInfo, false) == 0) cError = 5; else bBarsPrev = true;
161.                         break;
162.                      case eBarToTick   :
163.                         if (!BarsToTicks(szInfo)) 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.          }
186. //+------------------------------------------------------------------+
187. inline const bool TypeAccountIsHedging(void) const
188.          {
189.             return m_GlPrivate.AccountHedging;
190.          }
191. //+------------------------------------------------------------------+
192.       bool SetSymbolReplay(const string szFileConfig)
193.          {
194. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo)
195.             int         file;
196.             char        cError,
197.                         cStage;
198.             string      szInfo;
199.             bool        bBarsPrev;
200.                         
201.             if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
202.             {
203.                Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated...");
204.                return false;
205.             }
206.             Print("Loading data for playback. Please wait....");
207.             ArrayResize(Ticks.Rate, def_BarsDiary);
208.             Ticks.nRate = -1;
209.             Ticks.Rate[0].time = 0;
210.             cError = cStage = 0;
211.             bBarsPrev = false;
212.             m_GlPrivate.Line = 1;
213.             m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL;
214.             while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
215.             {
216.                switch (GetDefinition(FileReadString(file), szInfo))
217.                {
218.                   case Transcription_DEFINE:
219.                      cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
220.                      break;
221.                   case Transcription_INFO:
222.                      if (szInfo != "") switch (cStage)
223.                      {
224.                         case 0:
225.                            cError = 2;
226.                            break;
227.                         case 1:
228.                            if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array();
229.                            (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line);
230.                            break;
231.                         case 2:
232.                            if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array();
233.                            (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line);
234.                            break;
235.                         case 3:
236.                            if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array();
237.                            (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line);
238.                            break;
239.                         case 4:
240.                            if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array();
241.                            (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line);
242.                            break;
243.                         case 5:
244.                            if (!Configs(szInfo)) cError = 7;
245.                            break;
246.                      }
247.                      break;
248.                };
249.                m_GlPrivate.Line += (cError > 0 ? 0 : 1);
250.             }
251.             FileClose(file);
252.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick));
253.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay));
254.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev);
255.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev);
256.             switch(cError)
257.             {
258.                case 0:
259.                   if (GetInfoTicks().nTicks <= 0) //Atualizado ...
260.                   {
261.                      Print("There are no ticks to use. Service is being terminated...");
262.                      cError = -1;
263.                   }else if (!bBarsPrev) FirstBarNULL();
264.                   break;
265.                case 1   : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system...");    break;
266.                case 2   : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line);            break;
267.                default  : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line);
268.             }
269.                      
270.             return (cError == 0 ? !_StopFlag : false);
271. #undef macroFileName
272.          }
273. //+------------------------------------------------------------------+
274. };
275. //+------------------------------------------------------------------+

Código fuente del archivo C_ConfigService.mqh

Aunque se han realizado algunos cambios, algunos más sutiles que otros, como las partes que se destacaron, ahora notarás que ya no accederemos a los datos de los ticks directamente. La forma de hacerlo ahora es como se muestra en la línea 34. Observa que podemos acceder a los datos de una manera bastante interesante, pero sobre todo segura, ya que no podemos manipular los datos configurados en el archivo de script ni los presentes en los archivos de ticks o barras. Por lo tanto, ahora tenemos un sistema mucho más seguro y estable, que puede ser programado de manera considerablemente más sencilla y con muchas menos posibilidades de errores.

Sin embargo, además de esto, quiero llamar tu atención, estimado lector, hacia otro aspecto. Observa que se eliminaron las variables globales privadas entre las líneas 23 y 27. Aunque algunas de ellas se transladaron a la estructura que comienza en la línea 15. ¿Por qué hice este cambio respecto a las variables globales y privadas? La razón es que no quiero, o mejor dicho, no necesitamos que ciertos elementos formen parte de la configuración general del servicio de repetición/simulación.

Debido a este cambio en las líneas 23 a 27, se han modificado diversos puntos del código. Sin embargo, no merecen una atención especial, ya que son simples y fáciles de entender. Aun así, observa el constructor de la clase, que se encuentra en la línea 180. Nota que el código de este constructor es ligeramente diferente. Sin embargo, este cambio no es lo suficientemente relevante como para que sea necesario comentar el archivo de cabecera extensamente. Lo relevante está entre las líneas 207 y 209, que ahora aparecen destacadas.

Antes de corregir el problema de encapsulamiento de la clase C_FileTicks, la variable m_Ticks, que estaba en la cláusula protected, se inicializaba aquí, en estas líneas.

¿Te das cuenta de lo peligroso que era? Aunque el código funcionaba de manera aparentemente estable, no era realmente seguro como para implementar grandes modificaciones. Este fue un error mío que ha perdurado hasta ahora. Sin embargo, como necesito que las cosas funcionen de una manera muy específica, no es adecuado dejar que haya información que se filtre y pueda ser modificada en cualquier punto del código.

Con estas modificaciones, nuestras clases encargadas de mantener los ticks, las barras y de configurar el símbolo que se utilizará en la repetición/simulación ahora son completamente seguras, sin fugas de información. Es decir, hemos cerrado definitivamente esta parte relacionada con la carga y simulación de datos, así como con la configuración del símbolo que utilizará el servicio de repetición/simulación. Ahora podemos centrarnos en hacer que el servicio vuelva a funcionar y permitir al usuario posicionar la repetición/simulación para comenzar en un punto determinado, así como iniciar y pausar la simulación.

Estas funcionalidades ya existían antes de tantas modificaciones. Sin embargo, ahora las implementaremos de una manera mucho más segura, sencilla y estable. Esto también incluye la interacción entre los elementos con los que trabajaremos en un futuro cercano.


Permitimos al usuario iniciar y pausar la repetición/simulación

En este tema, te mostraré cómo hacer que el servicio se active cuando el usuario hace clic en los botones de reproducción y pausa. De esta manera, tanto la repetición como la simulación podrán llevarse a cabo.

Sin embargo, antes de continuar, permíteme mostrarte, estimado lector, dos pequeñas actualizaciones que se realizaron en el módulo del indicador de control. Estas actualizaciones tienen como objetivo evitar un problema bastante molesto que, en ocasiones, ocurre mientras el sistema está en funcionamiento. Dado que se trata de algo aleatorio, es difícil de demostrar, ya que puedes ejecutar el sistema muchas veces sin que ocurra nada extraño. Sin embargo, de vez en cuando, sucede algo inusual. Para evitar los inconvenientes provocados por estas fallas aleatorias, realicé algunos cambios en el código del módulo de control.

Por lo tanto, en el archivo de cabecera C_Replay.mqh, actualiza las siguientes funciones y procedimientos, que se muestran en los fragmentos a continuación. Comenzamos por el siguiente punto:

71. //+------------------------------------------------------------------+
72.       void SetPlay(bool state)
73.          {
74.             if (m_Section[ePlay].Btn == NULL)
75.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause);
76.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1));
77.             if (!state) CreateCtrlSlider();
78.          }
79. //+------------------------------------------------------------------+

Fragmento de código del archivo C_Controls.mqh

Debes añadir la línea 77 al código original. De esta manera, el módulo del indicador de control siempre sabrá cómo actuar y mostrará la barra del control deslizante si el botón de play está visible. Es decir, cuando estés en modo pausado, la barra del control deslizante aparecerá en el gráfico. A veces esto no sucedía, pero con esta actualización, la barra siempre se mostrará.

A continuación, es necesario realizar otro cambio. Este deberá hacerse en el fragmento mostrado a continuación:

170. //+------------------------------------------------------------------+
171.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
172.          {
173.             short x, y;
174.             static short iPinPosX = -1, six = -1, sps;
175.             uCast_Double info;
176.             
177.             switch (id)
178.             {
179.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
180.                   info.dValue = dparam;
181.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
182.                   x = (short) info._16b[0];
183.                   iPinPosX = m_Slider.Minimal = (x > def_MaxPosSlider ? def_MaxPosSlider : (x < iPinPosX ? iPinPosX : x));
184.                   SetPlay((short)(info._16b[1]) == SHORT_MAX);
185.                   break;
186.                case CHARTEVENT_OBJECT_DELETE:

Fragmento de código del archivo C_Controls.mqh

En este fragmento, deberás modificar el código original para incluir lo que se encuentra entre las líneas 180 y 185. El objetivo aquí es hacer que el módulo del indicador de control sea un poco más resistente a un tipo de falla bastante molesto. Esto sucede ocasionalmente cuando un evento personalizado provoca la ejecución de la línea 179, lo que desencadena comportamientos extraños. Uno de estos comportamientos es la aparición de valores erráticos en el índice uno del array. Originalmente, cuando esto ocurría, se registraba un error, y el módulo de control se eliminaba del gráfico, provocando así un fallo (CRASH) en el servicio de repetición/simulación. En otras palabras, el servicio se detenía debido a algo que ocurría de manera aleatoria en tiempo de ejecución, lo que hacía que apareciera un valor incorrecto.

Con la nueva implementación, si ocurre una falla, el módulo del indicador de control entrará de inmediato en modo pausado. Esto es mucho mejor que un fallo total del sistema de repetición/simulación. Además, hay un segundo detalle importante. Si no te has dado cuenta, en la línea 181 hay una pequeña comprobación. Esta garantiza que, incluso si aparecen valores erráticos, solo los utilizaremos si, y solo si, los dos primeros bytes de los ocho presentes en el valor double contienen algo específico. Si estos valores no están presentes, el evento será simplemente ignorado por el módulo del indicador de control. Este tipo de comprobación asegura la integridad de los datos o, al menos, confirma que estos provienen de una fuente específica: el servicio de repetición/simulación.

Tras realizar estas dos modificaciones, será necesario efectuar otra más. Pero esta vez en el código fuente del indicador. Dicha modificación puede verse en el fragmento que se encuentra a continuación.

41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.    (*control).DispatchMessage(id, lparam, dparam, sparam);
45.    if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown)
46.    {
47.       Print("Internal failure in the messaging system...");
48.       ChartClose(user00);
49.    }
50.    (*control).SetBuffer(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+

Fragmento del código fuente del indicador de control

Las líneas destacadas deberán eliminarse del código fuente. De este modo, elimina el archivo ejecutable del módulo de control, para que el servicio pueda forzar una nueva compilación del mismo cuando intentes realizar otra compilación del servicio. O, si prefieres, realiza una nueva compilación del módulo de control para garantizar que la versión más actualizada se incorpore como recurso del servicio de repetición/simulación.

Ahora podemos pasar al código del servicio de repetición/simulación. Haremos algunos cambios en el código existente hasta el momento, desde la última actualización realizada en el archivo de cabecera C_Replay.mqh. Estos cambios se detallaron en un artículo anterior. En ese momento, lo único que realmente se hacía era mostrar algunos datos cargados por el sistema junto con los módulos de control y de mouse. Sin embargo, a pesar de esto, no era posible realizar ninguna acción más allá de interactuar entre el indicador de control y el indicador de mouse, sin permitir iniciar o pausar el servicio de repetición/simulación para mostrar nuevos datos.

Pero aquí cambiaremos eso. Veamos cómo luce el nuevo archivo de cabecera C_Replay.mqh. El código puede verse íntegramente a continuación.

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

Código fuente del archivo C_Replay.mqh

Dado que este código necesitó algunos cambios, explicaré brevemente qué ocurrió y qué representan realmente estos cambios. Esto es para que el funcionamiento del sistema se comprenda adecuadamente. En esta versión, que se observa más arriba, hay algunos problemas que explicaré más adelante en este artículo.

Para empezar, decidí organizar la información en estructuras, ya que esto simplifica su separación de una manera más lógica. Observa la línea 20, donde declaro el handle para acceder al módulo del indicador de control. Esto evita un número excesivo de llamadas solo para capturar este valor, que se utilizará durante toda la vida útil del servicio. Por lo tanto, no tiene sentido hacerlo de otra manera, especialmente porque realizaremos numerosos accesos al módulo de control. Así, cualquier ahorro en tiempo entre las llamadas será muy beneficioso. Y pronto entenderás por qué.

El procedimiento presente entre las líneas 33 y 54 no sufrió grandes cambios, salvo por la eliminación del código que obtenía y liberaba el manejador para acceder al buffer del módulo de control. Aparte de esto, este procedimiento solo recibió un cambio derivado del fragmento modificado en el archivo de cabecera C_Replay.mqh.

Observa las líneas 50 y 51, donde incluyo la misma información que se espera en el evento personalizado dentro del manejador de mensajes de la clase C_Replay. Esto garantiza que la información transmitida por el servicio sea aceptada correctamente por el módulo de control.

El siguiente procedimiento se encuentra entre las líneas 67 y 93. Este procedimiento es el encargado de crear y enviar la información relativa a las barras y ticks hacia el símbolo personalizado. Es decir, aquí actualizamos los datos para que el MetaTrader 5 los muestre en el gráfico. Este procedimiento es prácticamente idéntico al de la última versión del servicio de repetición/simulación. En aquella etapa, cuando aún utilizábamos las variables globales del terminal para intercambiar mensajes entre aplicaciones, el proceso era similar. Sin embargo, tras corregir la fuga de información de la clase C_FileTicks, hemos tenido que reajustar algunos aspectos. Observa, por ejemplo, cómo accedemos a los datos en la clase C_FileTicks. Un ejemplo de esto está en la línea 75. Aunque este enfoque funciona, debemos considerar algo importante: el pequeño retraso que ocasiona cada una de estas llamadas. Pero hablaré de esto más adelante.

Fue necesario añadir otro procedimiento que ya existía en versiones muy antiguas del servicio. Este procedimiento, ubicado entre las líneas 95 y 120, tiene como propósito inicializar la presentación de las líneas de precios o cotizaciones. En el último artículo donde apareció este archivo de cabecera, C_Replay.mqh, no disponíamos de este procedimiento. Por este motivo, cuando el servicio de repetición/simulación indicaba al MetaTrader 5 que abriera el gráfico del símbolo personalizado, las líneas de cotización no eran visibles. Este procedimiento corrige precisamente esa deficiencia. Sin embargo, a diferencia de lo que podrías imaginar, este procedimiento solo se ejecuta una vez. Durante la fase en que el servicio está en modo reproducción, el encargado de actualizar esta presentación será el procedimiento CreateBarInReplay, ubicado en la línea 67. Específicamente, será en la línea 89 de este procedimiento donde se realice esta tarea.

Siguiendo con la revisión, llegamos al constructor de la clase. Aquí, básicamente, se añadieron unas líneas para inicializar ciertos datos. Estas líneas se encuentran entre las líneas 138 y 141. Fuera de eso, no hubo más cambios. Sin embargo, al observar el destructor de la clase, encontramos una nueva línea: la 146, donde liberamos el manejador. Aunque esto podría parecer irrelevante —ya que al cerrar el gráfico o finalizar el servicio, mantener estos datos no tendría demasiado sentido—, prefiero asegurarme de que el MetaTrader 5 sepa que el manejador ya no es necesario. Por esta razón, dicha línea fue añadida al código.

Ahora bien, si estamos liberando el manejador, significa que en algún momento lo cargamos. De hecho, esto sucede en el procedimiento situado entre las líneas 171 y 188. Un detalle adicional: en la línea 178 hacemos algo que antes no se hacía, es decir, llamamos al procedimiento que inicializa la presentación de las líneas de cotización. Luego, en la línea 180, capturamos el manejador que utilizaremos para acceder al buffer del indicador de control. El resto del procedimiento permanece igual.

Finalmente, llegamos a la última función de esta clase, ubicada entre las líneas 190 y 215. Tal vez esta sea la función que, en términos generales, más complicaciones nos cause. Esto se debe a que, a diferencia de otros procedimientos o funciones que pueden ejecutarse en tiempos más relajados —un procedimiento que tarda uno o dos segundos en completarse podría ser aceptable dentro de lo razonable—, esta función no permite tal flexibilidad. Aquí, la ejecución en tiempo real es crucial, y cualquier retraso o ineficiencia puede tener un impacto significativo en el rendimiento del servicio.

En el pasado, cuando todavía utilizábamos las variables globales del terminal, esta misma función nos causó bastantes dolores de cabeza para que funcionara adecuadamente. Fue necesario tomar diversas medidas, algunas incluso bastante drásticas, para que todo funcionara como debía. Ahora, aunque usamos un enfoque diferente, nuestro antiguo «verdugo», conocido como LoopEventOnTime, ha regresado para atormentarnos una vez más.

Antes de enfrentarnos a esta nueva prueba, echemos un vistazo a lo que estamos haciendo ahora.

Entre las líneas 194 y 198, tenemos un bucle. Este no nos genera ningún inconveniente, ya que su objetivo es esperar a que el usuario interactúe con el módulo de control utilizando el módulo de mouse, lo que permite al servicio iniciar la reproducción o la simulación. En caso de que el usuario cierre el gráfico o finalice el servicio, este bucle también se detendrá gracias a la verificación que realizamos utilizando la definición def_CheckLoopService. Esta definición fue declarada en la línea 9, donde puedes verificar que siempre estamos comprobando si el gráfico se cerró o si el servicio terminó. En resumen, este bucle no nos causa problemas.

Sin embargo, no podemos decir lo mismo del siguiente bucle, que comienza en la línea 200 y se extiende hasta la línea 212. Podrías pensar que este bucle es nuevo y que, por ello, podríamos tener problemas. No obstante, este bucle deriva directamente del que usábamos anteriormente con las variables globales del terminal. Cabe mencionar que este bucle actual es considerablemente más sencillo que aquel de las variables globales. Si los comparas, notarás que a este todavía le faltan algunos elementos para lograr el mismo comportamiento del servicio de repetición/simulación de entonces.

En cualquier caso, este bucle incluye un bucle interno. La razón de su existencia se explicó anteriormente, cuando mostré que, en ciertos escenarios, no podíamos interactuar con el sistema, viéndonos obligados a esperar a que un tick liberara el servicio. Te recomiendo revisar los primeros artículos de esta serie sobre la construcción del servicio de repetición/simulación para comprender mejor a qué me refiero.

Sin embargo, al observar este código, podrías pensar que todo está perfecto y que solo falta el código necesario para ajustar el punto de inicio de la repetición o simulación. Un poco de optimismo nunca viene mal. Pero antes de verificar el resultado, quiero mostrar el código del servicio. Aunque este no ha sufrido ningún cambio desde el último artículo, considero importante que lo veas y entiendas dónde está realmente nuestro problema.

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

Código fuente del servicio

Como puedes notar, no ha cambiado nada especialmente relevante en el código. 


Conclusión

Para que no necesites compilar y probar el servicio de repetición/simulación tú mismo, incluyo un video para este propósito. Obsérvalo con atención, ya que, si miras detenidamente, notarás una drástica caída en el rendimiento del servicio. ¿Por qué ha disminuido tanto el rendimiento? El motivo principal es que ahora hay una mayor cantidad de llamadas y se utilizan menos variables en el código. Sin embargo, este no es el único factor. Existen otros aspectos involucrados que también afectan al rendimiento. Dado que esto requiere tiempo y paciencia para explicarlo, analizarlo y probarlo, dejaré estas explicaciones para el próximo artículo.

El enfoque principal será precisamente el bucle presente en la función LoopEventOnTime. De alguna manera, será necesario optimizar ese bucle, responsable de generar y permitir que la repetición o simulación se ejecute, para que funcione de una manera más eficiente. Como mencioné a lo largo del artículo, hay elementos que están afectando el rendimiento del servicio en los momentos en que más se necesita. Durante la etapa en que se utilizaban variables globales del terminal, el rendimiento era aceptable. Sin embargo, lo que se observa en el video muestra que el rendimiento del nuevo modelo es completamente inaceptable.

Esto podría llevar a cualquiera a pensar que el sistema actual no es sostenible. Sin embargo, como programadores, nuestra tarea no solo consiste en resolver problemas relacionados con cálculos, sino también en abordar cuestiones de rendimiento. En general, el sistema de clases ha hecho que el código sea mucho más estable y seguro en su forma actual. Por lo tanto, nuestro verdadero desafío es lograr que el rendimiento vuelva a ser el mismo, o al menos cercano, al que teníamos durante la etapa en que usábamos variables globales del terminal, pero ahora aprovechando la comunicación mediante eventos y buffers. Prepárate para el próximo artículo, ya que será muy interesante.


Video de demostración


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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Hoàng Nghi
Hoàng Nghi | 18 ago 2024 en 10:23
¡Gracias por tu tiempo y gran esfuerzo para explicarlo todo, hermano!
Desarrollo de un sistema de repetición (Parte 61): Presionando play en el servicio (II) Desarrollo de un sistema de repetición (Parte 61): Presionando play en el servicio (II)
En este artículo, analizaremos las modificaciones necesarias para que el sistema de repetición/simulación pueda operar de manera más eficiente y segura. También mostraré algo de interés para quienes deseen aprovechar al máximo el uso de clases. Además, abordaré un problema específico de MQL5 que reduce el rendimiento del código al trabajar con clases y explicaré cómo resolverlo.
Redes neuronales: así de sencillo (Parte 93): Predicción adaptativa en los ámbitos de la frecuencia y el tiempo (Parte final) Redes neuronales: así de sencillo (Parte 93): Predicción adaptativa en los ámbitos de la frecuencia y el tiempo (Parte final)
En este artículo, continuamos la aplicación de los planteamientos del modelo ATFNet, que combina de forma adaptativa los resultados de 2 bloques (frecuencia y tiempo) dentro de la predicción de series temporales.
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.
Algoritmo de búsqueda cooperativa artificial (Artificial Cooperative Search, ACS) Algoritmo de búsqueda cooperativa artificial (Artificial Cooperative Search, ACS)
La búsqueda cooperativa artificial (Artificial Cooperative Search, ACS) es un método innovador que utiliza una matriz binaria y múltiples poblaciones dinámicas basadas en relaciones de mutualismo y cooperación para encontrar soluciones óptimas de forma rápida y precisa. El enfoque único de ACS sobre depredadores y presas le permite obtener excelentes resultados en problemas de optimización numérica.