Português
preview
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)

MetaTrader 5Ejemplos | 9 diciembre 2024, 13:03
133 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 60): Presionando play en el servicio (I)", realizamos algunos ajustes para que el sistema de repetición/simulador pudiera comenzar a lanzar nuevos datos en el gráfico. Aunque hicimos lo mínimo necesario para lograrlo, quedó claro que había ocurrido algo extraño. El sistema, que aparentemente no había sufrido cambios significativos, experimentó una caída importante de rendimiento. Esto da la impresión de que el sistema dejó de ser viable, ya que de repente se volvió muy lento. ¿Será realmente así? Y, de ser cierto, ¿cómo podemos resolver este problema? Siempre manteniendo los principios de la programación orientada a objetos.

Aunque efectivamente hubo una caída en el rendimiento, es posible solucionar gran parte de este problema ajustando y comprendiendo algunos aspectos clave del código. Tal vez en este artículo empiece a mostrar herramientas disponibles en el MetaEditor que pueden facilitar mucho el proceso de ajuste y mejora del código en el que estamos trabajando o desarrollando. Esto debería haberse mencionado antes en artículos anteriores, pero no lo consideré tan necesario como ahora, cuando es fundamental entender cómo funciona el código y por qué sufrió tal caída en su rendimiento sin cambios significativos en su lógica de trabajo.


Implementamos las mejoras más evidentes y directas

En muchos casos, el desconocimiento o la falta de una explicación más avanzada sobre el funcionamiento de MetaTrader 5 y MQL5 dificulta algunas implementaciones. Por fortuna, en la comunidad podemos compartir conocimientos que, aunque no siempre se apliquen de inmediato a lo que estamos desarrollando, siempre son bienvenidos.

Uno de estos puntos clave es el que intentaré explicar aquí. Gran parte de lo que expongo resulta más claro cuando realmente trabajas con MQL5 para aprovechar al máximo las capacidades de MetaTrader 5, algo que muchos no suelen conseguir.

Quizás uno de los aspectos menos comprendidos por muchos programadores de MQL5 sean los objetos gráficos. Se cree comúnmente que estos objetos solo pueden accederse, manipularse y ajustarse mediante algo presente en el gráfico, independientemente de que se trate de un indicador, script o EA. Sin embargo, esto está lejos de ser cierto.

Hasta el momento, hemos trabajado para que no se cree ningún tipo de dependencia entre lo que está en la ventana del gráfico del símbolo personalizado y lo que se está ejecutando en MetaTrader 5. Sin embargo, además de los métodos que estamos utilizando para transferir información entre las aplicaciones que se ejecutan en MetaTrader 5, existe la posibilidad de hacer algo un poco más elaborado, aunque arriesgado. No me malinterpreten, pero cuando hacemos las cosas generando alguna dependencia entre lo que se está ejecutando y lo que esperábamos que se ejecutara, pueden comenzar a ocurrir cosas extrañas.

Aunque muchas veces funcione, podríamos estar entrando en un camino tortuoso que podría llevarnos a toparnos con una pared y perder así un tiempo valioso que podría emplearse mejor en otras tareas. El motivo es que, en muchas ocasiones, dichos cambios imposibilitarán la adopción de nuevas medidas para mejorar o implementar algo. Para comprender lo que estoy proponiendo, es necesario tener conocimientos adecuados que permitan entender cómo funcionará el sistema.

El primer punto importante es que el módulo del indicador de control aparecerá únicamente si el servicio de repetición/simulación está en ejecución. No intentes colocar el módulo de control en el gráfico de forma manual, ya que esto invalidará todo lo que hemos hecho hasta ahora.

Segundo punto: los objetos gráficos creados por el módulo de control deberán seguir siempre una nomenclatura fija y muy estricta. De lo contrario, nos enfrentaríamos a serios problemas en el futuro.

Además de estos dos puntos, también implementaremos una medida que facilitará mucho la legibilidad del código. No tiene sentido utilizar símbolos o marcas que no signifiquen nada para nosotros. Sin embargo, estos cambios en la legibilidad estarán orientados más a facilitar la comprensión de ciertos ajustes que a hacer el código más rápido. Esto quedará más claro cuando se presenten los códigos fuente.

Los primeros cambios que realizaremos serán en el archivo de cabecera C_Controls.mqh. Pero antes de entender por qué es necesario realizar estos cambios, veamos cómo ha sido modificado. El nuevo código se muestra a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft        def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock   def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight       def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock  def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
024. #define def_PosXObjects         120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons         32
027. #define def_ColorFilter         0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Terminal.mqh"
030. #include "..\Auxiliar\C_Mouse.mqh"
031. //+------------------------------------------------------------------+
032. class C_Controls : private C_Terminal
033. {
034.    protected:
035.    private   :
036. //+------------------------------------------------------------------+
037.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
038.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
039. //+------------------------------------------------------------------+
040.       struct st_00
041.       {
042.          string  szBarSlider,
043.                  szBarSliderBlock;
044.          ushort  Minimal;
045.       }m_Slider;
046.       struct st_01
047.       {
048.          C_DrawImage *Btn;
049.          bool        state;
050.          short       x, y, w, h;
051.       }m_Section[eObjectControl::eNull];
052.       C_Mouse   *m_MousePtr;
053. //+------------------------------------------------------------------+
054. inline void CreteBarSlider(short x, short size)
055.          {
056.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
060.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
065.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
066.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
067.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
068.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
069.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
070.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
071.          }
072. //+------------------------------------------------------------------+
073.       void SetPlay(bool state)
074.          {
075.             if (m_Section[ePlay].Btn == NULL)
076.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
077.             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) ? 1 : 0));
078.             if (!state) CreateCtrlSlider();
079.          }
080. //+------------------------------------------------------------------+
081.       void CreateCtrlSlider(void)
082.          {
083.             CreteBarSlider(77, 436);
084.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.             PositionPinSlider(m_Slider.Minimal);
088.          }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.          {         
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.             {
095.                delete m_Section[c0].Btn;
096.                m_Section[c0].Btn = NULL;
097.             }
098.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
099.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.          }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(ushort p)
103.          {
104.             int iL, iR;
105.             
106.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.             m_Section[ePin].x += def_PosXObjects;
110.             m_Section[ePin].x += 95 - (def_SizeButtons / 2);
111.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
113.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
114.          }
115. //+------------------------------------------------------------------+
116. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
117.          {
118.             C_Mouse::st_Mouse InfoMouse;
119.             
120.             InfoMouse = (*m_MousePtr).GetInfoMouse();
121.             x = (short) InfoMouse.Position.X_Graphics;
122.             y = (short) InfoMouse.Position.Y_Graphics;
123.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
124.             {   
125.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
126.                   return c0;
127.             }
128.             
129.             return eNull;
130.          }
131. //+------------------------------------------------------------------+
132.    public   :
133. //+------------------------------------------------------------------+
134.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
135.          :C_Terminal(Arg0),
136.           m_MousePtr(MousePtr)
137.          {
138.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
139.             if (_LastError != ERR_SUCCESS) return;
140.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
141.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
142.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
143.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
144.             {
145.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
146.                m_Section[c0].y = 25;
147.                m_Section[c0].Btn = NULL;
148.             }
149.             m_Section[ePlay].x = def_PosXObjects;
150.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
151.             m_Section[eRight].x = m_Section[ePlay].x + 511;
152.             m_Slider.Minimal = eTriState;
153.          }
154. //+------------------------------------------------------------------+
155.       ~C_Controls()
156.          {
157.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
158.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
159.             delete m_MousePtr;
160.          }
161. //+------------------------------------------------------------------+
162.       void SetBuffer(const int rates_total, double &Buff[])
163.          {
164.             uCast_Double info;
165.             
166.             info._16b[eCtrlPosition] = m_Slider.Minimal;
167.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));//SHORT_MAX : SHORT_MIN);
168.             if (rates_total > 0)
169.                Buff[rates_total - 1] = info.dValue;
170.          }
171. //+------------------------------------------------------------------+
172.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
173.          {
174.             short x, y;
175.             static ushort iPinPosX = 0;
176.             static short six = -1, sps;
177.             uCast_Double info;
178.             
179.             switch (id)
180.             {
181.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
182.                   info.dValue = dparam;
183.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
184.                   x = (short) info._16b[eCtrlPosition];
185.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
186.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
187.                   break;
188.                case CHARTEVENT_OBJECT_DELETE:
189.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
190.                   {
191.                      if (sparam == def_ObjectCtrlName(ePlay))
192.                      {
193.                         delete m_Section[ePlay].Btn;
194.                         m_Section[ePlay].Btn = NULL;
195.                         SetPlay(m_Section[ePlay].state);
196.                      }else
197.                      {
198.                         RemoveCtrlSlider();
199.                         CreateCtrlSlider();
200.                      }
201.                   }
202.                   break;
203.                case CHARTEVENT_MOUSE_MOVE:
204.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
205.                   {
206.                      case ePlay:
207.                         SetPlay(!m_Section[ePlay].state);
208.                         if (m_Section[ePlay].state)
209.                         {
210.                            RemoveCtrlSlider();
211.                            m_Slider.Minimal = iPinPosX;
212.                         }else CreateCtrlSlider();
213.                         break;
214.                      case eLeft:
215.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
216.                         break;
217.                      case eRight:
218.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
219.                         break;
220.                      case ePin:
221.                         if (six == -1)
222.                         {
223.                            six = x;
224.                            sps = (short)iPinPosX;
225.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
226.                         }
227.                         iPinPosX = sps + x - six;
228.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
229.                         break;
230.                   }else if (six > 0)
231.                   {
232.                      six = -1;
233.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
234.                   }
235.                   break;
236.             }
237.             ChartRedraw(GetInfoTerminal().ID);
238.          }
239. //+------------------------------------------------------------------+
240. };
241. //+------------------------------------------------------------------+
242. #undef def_PosXObjects
243. #undef def_ButtonPlay
244. #undef def_ButtonPause
245. #undef def_ButtonLeft
246. #undef def_ButtonRight
247. #undef def_ButtonPin
248. #undef def_PathBMP
249. //+------------------------------------------------------------------+

Código fuente del archivo C_Controls.mqh

La manera de hacer que los objetos de control sigan un formato bastante estricto y, al mismo tiempo, sean fáciles de declarar es mediante la definición de la línea 23. Esta línea puede parecer extremadamente complicada, pero no te dejes llevar por su apariencia extravagante. En caso de duda, pruébala de forma aislada para entender su funcionamiento.

Ahora presta atención a algo importante. Observa que en las líneas 37 y 38 hay dos enumeraciones. La de la línea 37 no existía anteriormente, pero se creó para facilitar el acceso a los datos en el búfer. Puedes observar cómo se hace esto en el procedimiento SetBuffer, que se encuentra en la línea 162. Se realizó el mismo tipo de ajuste en el procedimiento de manejo de mensajes, aunque en este caso la implementación es ligeramente diferente. Esto puede observarse entre las líneas 182 y 186. Sin embargo, presta atención a la línea 184, ya que fue eliminada del código original.

Pero, volviendo a la cuestión de las enumeraciones, observa que la enumeración de la línea 38 ha cambiado respecto a su estado anterior. El motivo de este cambio es introducir ajustes que hagan el código más legible. De hecho, la variable de la línea 44, que antes era de un tipo con signo, ahora es de un tipo sin signo. Este tipo de modificaciones permite realizar pequeños cambios, como el observado en la línea 152, o algo similar a lo que puede verse en la línea 186.

Todo esto hace que el código sea un poco más legible, ya que la idea es implementar algo ligeramente diferente a lo anterior.

Muy bien, entonces veamos qué se hará en concreto. De cierta manera, esto podría ahorrar ciclos de máquina en el futuro. Pero primero debemos entender un detalle. En la línea 77, solicitamos cambiar la imagen representada en el objeto gráfico. Se trata de un botón que indica si estamos en modo "play" o "pause". Sin embargo, el servicio siempre observa el búfer del indicador de control, aunque podemos manejar esta funcionalidad de manera diferente en lo que respecta a saber si estamos en modo "play" o "pause". Esto está relacionado precisamente con el objeto manipulado en la línea 77.


Accedemos rápidamente al estado del botón

Como mencioné en el tema anterior, estos simples cambios no benefician al servicio lo suficiente como para lograr una mejora significativa en su rendimiento. Sin embargo, al analizar los puntos donde el servicio necesita realmente mejorar su rendimiento, la perspectiva cambia.

En el artículo anterior, mostré cuándo es necesario. Para refrescar tu memoria, el punto clave es la función LoopEventOnTime. Esta función llama a otra función con intervalos regulares para verificar el estado del botón del indicador de control y determinar si estamos en modo "pausa" o en modo "play".

Esta verificación se realiza inicialmente observando lo que está presente en el búfer del indicador de control. Sin embargo, existe una manera más elegante, aunque tiene otros problemas: observar directamente el objeto de control. Recuerda que el objeto de control es un OBJ_BITMAP_LABEL y este tipo de objeto tiene dos estados que pueden verificarse a través de una variable contenida en el objeto.

Al hacer esto, al inspeccionar el contenido de una variable específica dentro del objeto OBJ_BITMAP_LABEL en el gráfico, permitimos que el servicio ignore la lectura del búfer para determinar si debe iniciar o pausar el envío de datos al gráfico.

Sin embargo, si revisas el código del archivo de cabecera C_DrawImage.mqh, no encontrarás ninguna modificación en la variable necesaria del objeto OBJ_BITMAP_LABEL. Esto ocurre incluso antes de intentar hacer algo en el servicio. No obstante, si revisas el código del archivo C_Controls.mqh, verás que en la línea 77 se hace una solicitud para actualizar el objeto. A partir de este punto, podemos implementar los cambios necesarios para que el servicio se beneficie de ellos. En teoría, ya que el ahorro será de unos pocos ciclos de máquina por cada llamada.

Dado que los cambios no serán muchos, no reproduciré todo el código del archivo de cabecera aquí. Así que abre el archivo de cabecera C_DrawImage.mqh y modifícalo según el fragmento mostrado a continuación.

174. //+------------------------------------------------------------------+
175.       void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
176.          {
177.             
178.             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
179.             ReSizeImage(w, h, cView, what);
180.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
181.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
182.             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
183.             {
184.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName);
185.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName);
186.                ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_STATE, what == 1);
187.                ChartRedraw(GetInfoTerminal().ID);
188.             }
189.          }
190. //+------------------------------------------------------------------+

Fragmento del código fuente C_DrawImage.mqh

Observa algo importante: la línea 184 ha sido reemplazada por la línea 185, ya que esta incluye un parámetro que especifica el índice de la imagen. Sin embargo, lo que realmente nos interesa es la línea 186, que actualizará el estado de la variable del objeto OBJ_BITMAP_LABEL. Ahora, la variable OBJPROP_STATE del objeto reflejará directamente su estado. Recordemos que solo hay dos estados posibles. Play o Pause.

Con esto, podemos dirigir nuestra atención al código del archivo de cabecera C_Replay.mqh, donde se permite que el servicio acceda directamente al objeto y determine si estamos en modo de reproducción o pausa.

Para que el servicio pueda entender lo que está sucediendo, primero será necesario agregar algo nuevo. El primer cambio se encuentra justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_ConfigService.mqh"
05. #include "C_Controls.mqh"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
08. #resource "\\" + def_IndicatorControl
09. //+------------------------------------------------------------------+
10. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
11. //+------------------------------------------------------------------+
12. #define def_ShortNameIndControl "Market Replay Control"
13. //+------------------------------------------------------------------+
14. class C_Replay : public C_ConfigService
15. {
16.    private   :
17.       struct st00
18.       {
19.          C_Controls::eObjectControl Mode;
20.          uCast_Double               Memory;
21.          ushort                     Position;
22.          int                        Handle;
23.       }m_IndControl;

Fragmento del código fuente C_Replay.mqh

Observa que aquí, en la línea 5, hemos añadido una referencia al archivo de cabecera del indicador de control. No implementaremos nada que utilice directamente la clase de control, pero necesitamos acceder a las definiciones contenidas en dicho archivo. La principal es la que nos permite identificar los nombres de los objetos creados por la clase. No te preocupes, llegaremos a eso.

En este mismo fragmento hay otros cambios. Por ejemplo, en la línea 19, la variable es de un tipo diferente, lo que mejora la legibilidad del código. Además, se ha añadido una nueva variable en la línea 20. Esta variable se utilizará para almacenar ciertos valores provenientes del búfer del indicador de control. Sin embargo, no se utilizará exactamente como cabría esperar. Esto se aclarará más adelante. Una vez realizados estos cambios, debemos ajustar inmediatamente el constructor de la clase C_Replay. Dicho ajuste se muestra a continuación.

131. //+------------------------------------------------------------------+
132.       C_Replay()
133.          :C_ConfigService()
134.          {
135.             Print("************** Market Replay Service **************");
136.             srand(GetTickCount());
137.             SymbolSelect(def_SymbolReplay, false);
138.             CustomSymbolDelete(def_SymbolReplay);
139.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
140.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
141.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
142.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
143.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
144.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
145.             SymbolSelect(def_SymbolReplay, true);
146.             m_Infos.CountReplay = 0;
147.             m_IndControl.Handle = INVALID_HANDLE;
148.             m_IndControl.Mode = C_Controls::ePause;
149.             m_IndControl.Position = 0;
150.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
151.          }
152. //+------------------------------------------------------------------+

Fragmento del código fuente C_Replay.mqh

Observa cómo se inicializan los valores de la estructura m_IndControl. Es fundamental entender cómo se realiza esta inicialización y, sobre todo, por qué se utilizan esos valores y no otros. Aunque el motivo puede no ser evidente en este momento, lo será pronto. La idea es acceder al objeto presente en el gráfico, creado y mantenido por el módulo del indicador de control.

Para aprovechar realmente esta funcionalidad y acceder al objeto OBJ_BITMAP_LABEL directamente desde el gráfico mediante el servicio, será necesario modificar ligeramente el código UpdateIndicatorControl, ya existente en la clase C_Replay. La modificación puede verse en el fragmento siguiente.

34. //+------------------------------------------------------------------+
35. inline void UpdateIndicatorControl(void)
36.          {
37.             static bool bTest = false;
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (bTest)
44.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
45.                else
46.                {
47.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
48.                      m_IndControl.Memory.dValue = Buff[0];
49.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
50.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
51.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
52.                }
53.             }else
54.             {
55.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
56.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
57.                m_IndControl.Memory._8b[7] = 'D';
58.                m_IndControl.Memory._8b[6] = 'M';
59.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
60.                bTest = false;
61.             }
62.          }
63. //+------------------------------------------------------------------+

Fragmento del código fuente C_Replay.mqh

Verás que este código es muy diferente al anterior. La razón principal es implementar un enfoque más seguro para acceder a cualquier recurso relacionado con el módulo del indicador de control.

Para entender cómo funciona este fragmento, recuerda algunos puntos clave:

1) Los valores se inicializan en el constructor; 2) la primera rutina que llama a esta función es la que inicializa el módulo del indicador de control; 3) periódicamente, el procedimiento del bucle verifica el estado del botón en el indicador de control.

Estos tres puntos funcionan en secuencia, pero el tercero es el que más problemas causa. Este procedimiento es el que, de hecho, genera nuevos ticks en el gráfico y observa constantemente el indicador de control. Sin embargo, al poder leer directamente el estado del objeto presente en el gráfico y acceder al búfer del indicador solo en casos especiales, este procedimiento UpdateIndicatorControl podría evitar una pérdida de rendimiento en las fases críticas del servicio de repetición/simulación.

Veamos cómo funciona este fragmento. Primero, en la línea 40, verificamos si tenemos un manipulador válido. Si es correcto, continuamos. A continuación, en la línea 41, verificamos si el valor en memoria coincide con la posición actual. Si es así, en la línea 43 evaluamos si la variable estática es verdadera. En ese caso, utilizamos una función para acceder al valor contenido en el OBJ_BITMAP_LABEL. Presta mucha atención a cómo se realiza este acceso, ya que podrías encontrarlo peculiar, dado que hacemos referencia a algo definido en el archivo de cabecera C_Controls.mqh. Sin embargo, el acceso está correctamente implementado.

Si la variable estática es falsa, esto indica que se pueden realizar lecturas ligeramente más lentas de los datos. En este caso, utilizamos el búfer del indicador de control. Atención: no digo que la lectura del búfer sea realmente más lenta, pero compara el número de operaciones necesarias para leer una propiedad del objeto presente en el gráfico.

De todos modos, una vez leído el búfer, en la línea 49 verificaremos si no estamos en TriState. Si esta condición se cumple, ejecutaremos un conjunto de operaciones en la línea 50, antes de comprobar si estamos en modo play.Esto determinará si la variable estática será verdadera o falsa. Este conjunto de operaciones, que en realidad son asignaciones, está anidado de tal manera que el comando parece mucho más complejo de lo que realmente es. Sin embargo, dado que esto no afecta al compilador y los valores se asignan como se espera, podemos hacerlo así. Si la línea 50 es verdadera, almacenaremos el valor del búfer en la variable de posición interna. Esto ocurre en la línea 51.

Este tipo de acción solo sucederá en una situación: cuando el usuario interactúa con el control deslizante y cambia la posición desde la que debe iniciarse la repetición/simulación. Es decir, cuando estamos en modo de reproducción, este código no se verifica. Sin embargo, cuando pasamos del modo pause al modo play, este código se ejecuta, lo cual será importante más adelante.

Por otro lado, si la comprobación de la línea 41 resulta falsa, se ejecutará lo que está definido entre las líneas 55 y 60. Esto disparará un evento personalizado para que podamos actualizar el módulo del indicador de control. Y así es como funcionará el sistema a partir de ahora.

Tal vez leer directamente el objeto en el gráfico no haga que el servicio sea realmente más eficiente en términos de rendimiento. Pero abre la posibilidad de realizar manipulaciones más avanzadas en los objetos del gráfico. Esto permite construir sistemas mucho más elaborados sin sobrecargar la plataforma MetaTrader 5, evitando llenar el gráfico con indicadores y otros elementos únicamente para manipular los objetos en el gráfico.


Ganamos rendimiento de forma tangible

A pesar de todo lo mencionado hasta ahora, estas modificaciones no suponen un aumento significativo del rendimiento del servicio de repetición/simulador. Al menos, no se observa un cambio drástico. Sin embargo, estas alteraciones hacen que ciertas partes del código sean más eficientes y, sobre todo, más legibles. Este último aspecto se debe a las definiciones creadas en el archivo C_Controls.mqh, que luego se utilizaron en el archivo de cabecera C_Replay.mqh. Aunque no hayas visto todo el código completo, probablemente ya tengas una idea de cómo modificar ciertas secciones para mejorar la legibilidad de la clase C_Replay.

Ahora bien, veamos algo que realmente mejora el rendimiento del código. Esto busca hacer que vuelva a ejecutarse de manera que pueda generar una barra de 1 minuto dentro del tiempo esperado.

Puede parecer extraño y, a simple vista, carecer de sentido lo que voy a mostrarte. Pero créeme, pruébalo por ti mismo y comprueba que hay una mejora sustancial en el rendimiento con un cambio sencillo. Para verlo, observemos ahora el código completo del archivo de cabecera C_Replay.mqh. A continuación, se muestra en su totalidad. La numeración será un poco diferente a la de los fragmentos anteriores, pero no te preocupes por ello. Centrémonos en el código completo.

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

Código fuente del archivo C_Replay.mqh

Presta mucha, pero mucha atención a lo que voy a explicar. Porque si no comprendes los detalles involucrados, te sentirás completamente perdido con lo que haremos para mejorar el rendimiento del servicio de repetición/simulador.

En la línea 32, definimos una nueva variable privada de la clase C_Replay. Sin embargo, la estructura utilizada está definida en el archivo de cabecera C_FileTicks.mqh. Para más detalles sobre esto, consulta el artículo anterior. Esta variable se utiliza en varios puntos considerados críticos dentro de la clase C_Replay. Estos puntos se seleccionaron debido a la frecuencia con la que se invocan durante la ejecución del servicio. Uno de ellos es el procedimiento CreateBarInReplay, que se usa para convertir ticks en barras en el gráfico. Otro punto clave es la función LoopEventOnTime, donde, en la línea 207, inicializamos esta misma variable.

Un punto importante es que, cuando en la línea 207 utilizamos la función GetInfoTicks para capturar los valores presentes en la clase C_FileTicks, que también forman parte de una variable privada de esta clase, no estamos creando ningún tipo de puntero para manipular dichos datos. En su lugar, al ejecutar la línea 207, duplicamos la información contenida en la variable privada de dicha clase y la almacenamos en una nueva variable que será privada de la clase C_Replay.

Soy consciente de que este enfoque puede que no parezca ideal ni ayude directamente a mejorar la eficiencia del servicio. De hecho, implica un mayor coste debido al uso de más memoria del sistema. Sin embargo, acceder a los datos mediante variables es significativamente más rápido que a través de llamadas a procedimientos o funciones. Esto mejora el rendimiento del servicio. Si MQL5 permitiera el uso de punteros, podríamos almacenar los datos en una región común de memoria. En este caso, la clase C_FileTicks tendría acceso tanto de lectura como de escritura, mientras que otras clases o partes del código solo tendrían acceso de lectura. De este modo, se mantendría el consumo de memoria bajo y se garantizaría un rendimiento adecuado, al tiempo que se respetaría el encapsulamiento de las variables dentro de las clases.

El uso de punteros para lograr esto se implementa mejor mediante funciones. Este enfoque se mostró en la versión discutida en el artículo anterior. Sin embargo, al poner en funcionamiento el servicio de repetición/simulador, especialmente en el modo simulación, donde las barras se transforman en ticks y luego vuelven a convertirse en barras, observé una caída considerable en el rendimiento del servicio. Esto puede deberse a que, en el momento de escribir este artículo, el compilador de MQL5 no reconoce esta construcción de funciones como un puntero que permita acceder restringido a una estructura determinada. En este caso, la clase base, C_FileTicks, podría acceder a la estructura con permisos de lectura y escritura, mientras que cualquier otro código que no fuera de la clase tendría solo acceso de lectura. Es posible que, cuando se lea este artículo, los desarrolladores del compilador de MQL5 ya hayan solucionado esta cuestión. Esto permitiría lograr el mismo rendimiento que con el esquema descrito aquí. Una recomendación importante. Dado que este artículo se escribió hace algún tiempo, te sugiero que pruebes el código antes de implementarlo. Esto se debe a que es posible que ya no sea necesario realizar lo que se muestra aquí.

Sin embargo, no culpo a los creadores y desarrolladores del compilador de MQL5, ya que no he visto una construcción similar en ningún otro lugar. Sin embargo, esta limitación me obligó a cambiar de enfoque y tomar una decisión:

  • Romper el encapsulamiento establecido en el artículo anterior.
  • Implementar la solución duplicando los datos. 

Opté por duplicar los datos, ya que la memoria es un recurso barato. Romper el encapsulamiento habría dificultado el desarrollo y mejora del código. Con este enfoque, logramos un aumento de velocidad de ejecución de aproximadamente cuatro a cinco veces, manteniendo el mismo nivel de seguridad en los datos. Durante las pruebas realizadas antes de implementar la duplicación de datos, logramos procesar cerca de 3 segundos de ticks utilizando el dólar como ejemplo. Estos resultados pueden consultarse en los artículos de la primera fase. Ahora, con la duplicación, conseguimos procesar casi 15 segundos de datos en el mismo contexto. Cabe destacar que estos resultados se aplican a la simulación. En el caso de la repetición, el sistema ahora construye la barra de 1 minuto dentro de un tiempo aceptable, es decir, cercano a los 60 segundos.

Un detalle importante. Es posible que pienses que la función Sleep, presente en la línea 216, podría estar ralentizando el sistema y que, al eliminarla, podríamos procesar más datos en el gráfico. Sin embargo, al trabajar con ticks reales, la función Sleep en la línea 216 es imprescindible. Si no estuviera, los datos se lanzarían al gráfico mucho más rápido de lo normal. Esto haría que la reproducción, y no la simulación, generara la barra de un minuto antes de que transcurrieran los 60 segundos correspondientes.


Conclusión

Ahora nos enfrentamos al siguiente problema: durante una simulación, es posible que se procesen menos ticks de los necesarios en el intervalo de 60 segundos. Sin embargo, en el caso de la repetición, los lanzamientos no se retrasan significativamente. Por lo tanto, en el contexto de la repetición, el sistema sigue operando bajo los mismos principios que en la fase anterior, cuando utilizábamos las variables globales del terminal. Para la simulación, sin embargo, será necesario realizar ciertos ajustes.

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

Archivos adjuntos |
Anexo.zip (420.65 KB)
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.
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)
Llevamos bastante tiempo trabajando únicamente con los indicadores. Pero ahora ha llegado el momento de hacer que el servicio vuelva a ejecutar su trabajo y podamos ver el gráfico construyéndose con los datos proporcionados. Sin embargo, como no todo es tan simple, será necesario observar para entender lo que nos espera.
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.
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.