Português
preview
Desarrollo de un sistema de repetición (Parte 55): Módulo de control

Desarrollo de un sistema de repetición (Parte 55): Módulo de control

MetaTrader 5Ejemplos | 29 julio 2024, 10:00
17 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Desarrollo de un sistema de repetición (Parte 54): El nacimiento del primer módulo, realizamos el ensamblaje del primer módulo real de todo nuestro nuevo sistema de repetición/simulador. Además de poder ser utilizado en el sistema que se está desarrollando, también podremos hacer uso de los módulos de forma personal y personalizada, para evitar la necesidad de una gran programación con el fin de generar dicho sistema. Sea cual sea, ya que una vez que el módulo ha sido construido, podemos ajustarlo fácilmente sin necesidad de una nueva compilación. 

Para hacer esto, será necesario enviar un mensaje dentro del módulo para poder cambiar su apariencia o forma de funcionar, algo que puede hacerse fácilmente mediante un script sencillo.

Muy bien, entonces, basándonos en lo visto en los últimos artículos, tenemos la posibilidad de producir un sistema que puede usarse tanto en una cuenta real como en una de demostración. Además, podremos hacer muchas más cosas, como crear un sistema de repetición/simulador que se comporte de manera bastante similar a lo que se vería en una cuenta real o de demostración.

Pero la principal ventaja de este nuevo modelo que empezaremos a utilizar es el hecho de que podrás usar las mismas herramientas y aplicaciones tanto en el sistema de repetición/simulador, como en el día a día, usando la plataforma MetaTrader 5 para estudiar en la cuenta de demostración o incluso ejecutando operaciones en una cuenta real.

Ahora que nuestro indicador de mouse ya está concluido. Podemos comenzar a construir, o mejor dicho, a adecuar nuestro indicador de control para que trabaje de forma modular. Vale la pena explicar brevemente lo que acabo de mencionar.

Hasta hace poco, el sistema de repetición/simulador utilizaba variables globales del terminal. Esto se debía a que necesitábamos comunicarnos e interactuar entre nosotros para poder controlar y acceder al servicio de repetición/simulación.

A partir de que empezamos a usar un sistema modular en el que el intercambio de mensajes se realiza a través de eventos personalizados, ya no necesitaremos utilizar las variables globales del terminal. Por esta razón, ahora podremos eliminar todas las variables globales del terminal que antes se usaban. Sin embargo, al hacer esto, necesitaremos adaptar las cosas para que la información continúe fluyendo entre los programas.

Hacer esto, es decir, modelar el sistema de transferencia de información, es una tarea que debemos realizar con bastante calma, ya que no existe la posibilidad de que la información pueda leerse posteriormente. Si el programa o la aplicación no están presentes en el gráfico en el momento en que la información llega a través de un evento personalizado, se perderá. Es decir, si el programa o la aplicación no están presentes en el gráfico en el momento en que la información llega a través de un evento personalizado, se perderá.Por lo tanto, son necesarios mecanismos adicionales para volver a enviar la información hasta que tengamos la garantía de que realmente fue capturada por la aplicación o programa deseado.

En función de este criterio, se decidió que el sistema de repetición/simulador debería contar con tres programas principales para poder funcionar mínimamente. Así, podrá funcionar mínimamente. De estos programas, solo dos serán visibles para el usuario: el programa responsable del servicio en sí y el indicador de mouse. El indicador de control se tratará como un recurso del programa de servicio, por lo que no podrá usarse sin que el servicio esté siendo prestado.

Dada esta pequeña explicación, veamos cómo se ha modificado el indicador de control para que pueda empezar a realizar su trabajo. Y este es precisamente controlar el servicio de repetición/simulador.


Modificación del indicador de control

Los cambios que se necesitaban hacer en el indicador de control no fueron tantos, ya que durante la fase anterior, en la que comenzábamos a eliminar las variables globales del terminal, ya se habían realizado muchos cambios. No obstante, hay algunas cosas que, sin una comprensión adecuada de cómo se producirá el intercambio de mensajes, impiden entender cómo el sistema puede hacer lo que hará.

Entonces, intentemos entender las cosas desde el principio para que no te pierdas en los próximos artículos que aún quedan por publicar.

Cuando el indicador se coloca en el gráfico, el usuario puede configurar algunas cosas. Estas forman parte de las variables de entrada del indicador. Sin embargo, en ciertos momentos, estas mismas variables son más un obstáculo que una ayuda. No me malinterpretes, no estoy promoviendo una radicalización de las cosas. Pero cuando permitimos que el usuario acceda a una variable para que el indicador (en este caso) se configure previamente, estamos abriendo una puerta para que surjan problemas.

Descartando el hecho de que el usuario pueda modificar o tocar donde no debería, estas mismas variables nos ayudan bastante. Tanto es así que las usamos para informar de la ID del gráfico al indicador. No es que un indicador necesite realmente esta información. Pero debes recordar que, cuando el indicador se coloca en el gráfico, su ID puede ser diferente de la esperada por los objetos. Esto se vio en un artículo anterior de esta misma serie,

pero a pesar de que podemos usar el sistema de mensajes para transmitir la ID al indicador. Y sí, podríamos hacerlo, ya que el servicio es quien abrirá el gráfico y, por tanto, sabe cuál es la ID de este. Este tipo de cosa solo complicaría innecesariamente el código, tanto el del servicio como el del indicador. Por este motivo, mantendré las cosas funcionando como hemos hecho hasta aquí. Sin embargo, necesitaremos hacer algunos pequeños cambios en el código del indicador de control, ya que no usaremos ninguna variable global del terminal para pasar los datos a él.

Bien, a continuación, puedes ver el código del archivo C_Control.mqh completo. Dado que gran parte del código ya se explicó en artículos anteriores, nos centraremos solo en las partes nuevas y que tienen alguna redundancia para mencionarlas y explicarlas.

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

Código fuente del archivo C_Control.mqh

Puedes ver algunas cosas aparentemente extrañas en el código. La primera de ellas está en la línea 150, donde indicamos un valor para el desplazamiento mínimo del control deslizante. Lo hacemos mediante una constante definida dentro del MQL5, INT_MIN. Este valor es negativo, el más negativo que podemos tener en una variable entera. Pero, ¿por qué lo hice? Entender el motivo ahora es un poco complicado, así que te pido paciencia, ya que para entender realmente el motivo de esta línea 150, será necesario comprender otras cosas también.

La siguiente cosa que merece la pena destacar se encuentra en la línea 160, donde tenemos una rutina para escribir en el buffer del indicador de control. Básicamente escribiremos solo dos valores, al menos por ahora. No sé si necesitaremos escribir más valores, pero estos dos valores estarán compactados en un valor del tipo double, por lo que usaremos solo una posición dentro del buffer.

Para hacer esta compactación, usaremos una unión que se declara en la línea 162. Luego, en la línea 164, colocamos el valor que será ajustado por el usuario cuando este manipule el control deslizante. Atención al siguiente detalle: Estaremos almacenando la posición del control que ha sido modificada por el usuario. En la línea 165, informaremos del estado del indicador de control, es decir, si se encuentra en modo de reproducción o en modo pausa.

Mucha atención aquí. Observa que, cuando indicamos que el usuario está en modo «play», es decir, que ha presionado el botón de reproducir, almacenaremos un valor. Cuando estemos en modo pausa, usaremos otro valor. Los valores que usaremos son los extremos del límite posible para el tipo entero. De esta forma, evitamos la ambigüedad de usar el valor cero y, al mismo tiempo, garantizamos la integridad de la información, lo que facilita su verificación posterior.

Este tipo de cosas es muy importante. Si no reflexionas con calma durante la fase de construcción del indicador de control, tendrás problemas después. Debes recordar algo: la información no irá directamente del indicador de control al servicio, tendrá que pasar por un canal, y este canal que usaremos es el buffer. Pero volveré a este punto pronto, ya que será necesario explicar algunos detalles al respecto.

Al final de la línea 166, almacenaremos el valor compactado en una posición específica del búfer del indicador. Ya he explicado en otro artículo de esta misma serie el motivo de almacenar los datos en esta posición, así que, en caso de dudas, lee los artículos anteriores de esta serie para resolver cualquier cuestión al respecto.

Lo siguiente que realmente merece ser explicado en este código se encuentra en el manejador de mensajes. Este comienza en la línea 169. Hay dos puntos que merecen destacarse. Empecemos por el que menos cosas implica y que está relacionado con lo explicado anteriormente. Así que vayamos a la línea 209.

En esta línea hay algo curioso. Almacenamos el valor ajustado por el usuario al mover el control deslizante en la variable m_Slider.Minimal. El motivo es precisamente para simplificar las cosas, de modo que podamos centralizar todo en puntos clave del código. Si esta línea 209 no existiera, tendríamos que realizar alguna prueba en un punto del código para transferir al buffer la posición ajustada por el usuario. O peor, tendríamos que encontrar alguna manera de pasar el valor ajustado por el usuario al servicio sin usar variables globales del terminal. Antes, esto lo hacía precisamente una variable, pero ahora usaremos un buffer. Así que para no estar probando y reajustando las cosas, colocamos el valor en un lugar que será fácilmente accesible. Detalle: Este valor solo se almacenará en este punto cuando el usuario presione el botón de reproducir.

Dada esta explicación, podemos retroceder un poco en el código y ver la línea 177, donde encontramos un evento personalizado cuya definición es inicializar el indicador de control para que el control deslizante pueda estar adecuadamente ajustado.

Este evento personalizado se ejecutará de vez en cuando, pero presta atención aquí. Observa que el valor que contiene los datos está presente dentro de un valor double y de forma compactada. Por tanto, necesitamos traducir esta información al mismo tiempo que debemos garantizar su seguridad y fiabilidad. La información recibida, en cierta manera, sigue el mismo principio que la información que almacenaremos en el buffer. Pero hay un detalle: Aquí verificaremos su integridad. 

Observa que en la línea 180 hay una pequeña prueba. Esta verificará si el valor que indica si el sistema está en modo reproducir o pausa es cero. Si esto sucede, significa que algo va mal y que se está informando erróneamente al indicador de control, y ha ocurrido un error. Por esta razón, tenemos la función SetUserError. Normalmente no se ejecutará, pero si lo hace, tendremos que tomar las medidas necesarias. Esto se verá más adelante, en el código del indicador.

Entonces, si todo está correcto, haremos dos cosas más. La primera es una llamada a través de la línea 182 al procedimiento responsable de mostrar el botón de reproducir o pausar. La segunda es una prueba vista en la línea 183. Si el valor es el mínimo, esto indica que estaremos en modo pausado, por lo que necesitamos recrear la barra de control deslizante para que el usuario pueda ajustar las cosas.

Básicamente, esto es todo. En el momento en que el indicador se lance en el gráfico, no funcionará hasta que un evento personalizado lo inicialice. Pero la forma en que esto se hará se verá después. La interacción entre el indicador de mouse y el indicador de control generará todo el ciclo de mensajes, de modo que el servicio de repetición/simulador pueda controlarse efectivamente.

Veamos ahora el código del indicador de control. Se muestra a continuación, en su totalidad. Presta mucha atención a la explicación de este código, ya que en este punto se sentarán las bases de lo que se verá en futuros módulos de este mismo sistema.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.55"
07. #property link "https://www.mql5.com/pt/articles/11988"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    ResetLastError();   
24.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
25.       SetUserError(C_Terminal::ERR_PointerInvalid);
26.    if (_LastError != ERR_SUCCESS)
27.    {
28.       Print("Control indicator failed on initialization.");
29.       return INIT_FAILED;
30.    }
31.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
32.    ArrayInitialize(m_Buff, EMPTY_VALUE);
33.    
34.    return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.    return m_RatesTotal = rates_total;
40. }
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).SetBuff(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+
53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay/simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }
68. //+------------------------------------------------------------------+

Código fuente del indicador de control

Presta atención a la línea 10, donde le indicamos al MQL5 que necesitaremos un buffer. Sin embargo, en la línea 9 le indicamos que no graficaremos ninguna información en el gráfico, es decir, el buffer será interno del indicador y no será visible para el usuario, aunque cualquier código que sepa cómo acceder a él podrá hacerlo.

Dado que usaremos un buffer, necesitamos declararlo. Primero lo hacemos en la línea 18 y, luego, en la línea 31 declaramos el buffer para que sea accesible fuera del indicador. En la línea 32, garantizamos que el buffer esté completamente vacío. Ahora presta mucha atención, porque esto es importante.

Cuando el usuario interactúa con el marco temporal en la plataforma MetaTrader 5, esta elimina todo lo que hay en el gráfico y luego lo repone. Al hacer esto, se reinicia todo el código. En el caso de los indicadores, se produce una nueva llamada al evento OnInit, que ejecuta todo el proceso de reinicialización del indicador. Nuestro indicador de control no hace prácticamente nada, o mejor dicho, no ejecuta ningún cálculo específico. Su única utilidad es proporcionar medios para que el usuario interactúe y controle el servicio que se usa para lanzar las barras en el gráfico en un activo personalizado.

Entonces, cuando el usuario modifica el marco temporal, todos los valores dentro del indicador se perderán. Necesitamos garantizar que esto suceda, pero debe hacerse de manera controlada. El servicio, cuya ejecución está controlada por el indicador, no tiene idea de lo que está sucediendo en el gráfico, al igual que el indicador no sabe lo que el servicio está haciendo. La forma de hacer que ambos, tanto el servicio como el indicador, sepan lo que cada uno está haciendo, es a través de mensajes que se intercambian entre ellos.

Antes, este intercambio de mensajes se hacía a través de una variable global del terminal. Pero, ahora estamos creando una forma diferente y necesitamos garantizar que, independientemente de lo que el usuario haga en el gráfico, tanto el indicador como el servicio estén alineados y sean conscientes de lo que está sucediendo. Entonces, para que el servicio sepa lo que está sucediendo con el indicador, usamos el buffer del indicador. Ahí colocamos toda la información relacionada con lo que está sucediendo en el indicador.

La forma en que el indicador sabe lo que el servicio está haciendo es a través de parámetros de entrada, es decir, los declarados en la línea 16 y los eventos personalizados, que se manejan dentro de la llamada OnChartEvent del indicador.

Enviar datos al indicador a través de parámetros de entrada después de que ya esté presente en el gráfico es algo inviable. No estoy diciendo que no se pueda hacer, pero es algo impracticable y no es algo que vayamos a hacer. Una vez que el servicio coloque el indicador en el gráfico, perderás la posibilidad de comunicarte con él a través de parámetros de entrada. Por lo tanto, dependemos de los eventos personalizados.

Ahora viene el detalle. Cuando el usuario cambia el marco temporal, el indicador deja de saber cuál era el estado que se estaba utilizando y cuál era la posición de desplazamiento que ya se había alcanzado. Sin embargo, el servicio conoce esta información. Pero, ¿cómo puede el servicio informar de estos datos al indicador para que se mantenga íntegro? El servicio no sabe cuándo el MetaTrader 5 repone los indicadores en el gráfico, pero el servicio puede ver el buffer del indicador. Y aquí está el truco.

Cuando se coloca el indicador en el gráfico, su buffer estará inicialmente en cero. En el momento en que se ejecute el constructor de la clase C_Controls, se inicializará un valor extraño en la línea 150 (véase el código de la clase para comprenderlo). Pero este valor no se colocará en el buffer hasta que se llame a OnChartEvent, momento en el que, en la línea 50 del código del indicador, el buffer se modificará.

Entonces, cuando el servicio lea el buffer después de que el MetaTrader 5 haya repuesto el indicador de control en el gráfico, verá valores cero o una cantidad extraña. En ese momento, se disparará un evento personalizado para el MetaTrader 5, de manera que el servicio informará al indicador de los valores actualizados para que se grafique correctamente en la pantalla. Así, nuevamente tendremos la información de los botones y controles deslizantes graficados correctamente.

Si intentáramos hacer esto de otra manera, tendríamos que idear alguna forma de reponer esta misma información que se perdió. Esto podría llevarnos a crear diversas soluciones diferentes, todas con el mismo resultado: la inicialización del indicador. Sin embargo, algunas de estas soluciones podrían ser manipuladas por el usuario, lo que complicaría la comunicación entre el servicio que se está controlando y el indicador que controla el servicio. No obstante, al hacerlo de esta manera, trasladamos toda la complicación a la comunicación entre el servicio y el indicador. Es decir, creamos una capa extra de seguridad, al mismo tiempo que controlamos la forma de garantizar que la información solo sea leída por quien realmente necesita leerla y hacer uso de su contenido.

Lo que acabo de explicar aquí puede parecer muy confuso y bastante exótico para la mayoría. Pero, principalmente, para quienes están empezando en la programación, ya que esta idea de intercambio de mensajes e inicialización controlada es algo de lo que seguro que muchos no han oído hablar. Entonces, ¿cómo podemos demostrar que esto realmente funciona en la práctica? Para ello, utilizaremos el indicador de control y el indicador de mouse. Pero necesitaremos crear algo solo para demostrar y entender la idea antes de ver el sistema real en funcionamiento.

Para lograrlo, usaremos un código mucho más sencillo, pero lo suficientemente eficiente para que la idea que hay detrás de todo lo que se ha mostrado aquí quede más clara. A continuación, ve el código que se utilizará.

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. #property version   "1.00"
06. //+------------------------------------------------------------------+
07. #include <Market Replay\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
10. #resource "\\" + def_IndicatorControl
11. //+------------------------------------------------------------------+
12. input string user00 = "BOVA11";    //Symbol
13. //+------------------------------------------------------------------+
14. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
15. //+------------------------------------------------------------------+
16. void OnStart()
17. {
18.    uCast_Double info;
19.    long id;
20.    int handle, iPos, iMode;
21.    double Buff[];
22.    
23.    SymbolSelect(user00, true);
24.    id = ChartOpen(user00, PERIOD_H1);            
25.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE)
26.       ChartIndicatorAdd(id, 0, handle);
27.    IndicatorRelease(handle);
28.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE)
29.       ChartIndicatorAdd(id, 0, handle);
30.    IndicatorRelease(handle);   
31.    Print("Service maintaining sync state...");
32.    iPos = 0;
33.    iMode = INT_MIN;
34.    while (def_Loop)
35.    {
36.       while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50);
37.       info.dValue = 0;
38.       if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0];
39.       IndicatorRelease(handle);
40.       if (info._int[0] == INT_MIN)
41.       {
42.          info._int[0] = iPos;
43.          info._int[1] = iMode;
44.          EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, "");
45.       }else if (info._int[1] != 0)
46.       {
47.          iPos = info._int[0];
48.          iMode = info._int[1];
49.       }
50.       Sleep(250);
51.    }
52.    ChartClose(id);
53.    Print("Finished service...");   
54. }
55. //+------------------------------------------------------------------+

Código fuente del Servicio de Demostración

Atención a la línea 10, en la que convertimos el indicador de control en un recurso del servicio. De esta forma, no es necesario incluirlo en la lista de indicadores, ya que no tiene utilidad alguna para el sistema que se está implementando. En la línea 12, informamos de un activo para poder probar el sistema. Un detalle: utiliza un activo válido, ya que no probaremos esto después. En la línea 14, tenemos una definición que servirá para probar algunas condiciones con el fin de poder finalizar el servicio de manera adecuada.

En la línea 23, colocamos el activo en la ventana de observación de mercado, en caso de que no se encuentre allí. En la línea 24, abriremos una ventana gráfica que contendrá el activo que hayas indicado como usuario. Una vez hecho esto, tendremos la ID del gráfico y podremos usarla para colocar los indicadores en el gráfico.

Entonces, en la línea 25, colocaremos el indicador de control en el gráfico que se acaba de abrir. En la línea 29, añadiremos el indicador de mouse. Ten en cuenta que el indicador de mouse estará en la lista y el indicador de control no. Pero necesitamos ambos para someterlas a prueba.

En la línea 31, informamos al terminal de que el servicio estará activo y de que monitorizará lo que ocurra en el gráfico.

Hasta este momento, el indicador de mouse ya debería ser visible en el gráfico. Sin embargo, el indicador de control no estará visible, aunque ya se encuentre listado entre los indicadores presentes en el gráfico. No te olvides de la explicación que di sobre cómo se inicializa el indicador de control. En este momento, su buffer tendrá valores extraños y sin ninguna representatividad para nosotros. Por esta razón, no podemos interactuar con él. Pero si el indicador de control se ha inicializado correctamente y el buffer ya se ha escrito, obtendremos un valor bastante específico. Entonces, en la línea 34, entraremos en un bucle que se mantendrá mientras se mantengan las condiciones definidas en la línea 14.

Ahora, un detalle: En la línea 36, verificamos si el indicador de control de hechos ya se encuentra en el gráfico. ¿Pero por qué hacer esta prueba y esperar a que termine? El motivo es que el código se puede ejecutar mucho más rápido de lo que suceden las cosas en realidad. Entonces, necesitamos permitir de alguna manera que MetaTrader 5 estabilice las cosas; por eso hacemos este bucle en la línea 36.

Una vez que todo esté en orden, intentamos leer el buffer del indicador de control. Quiero recordarte nuevamente que el indicador aún no estará visible en el gráfico.

Si se realiza la lectura, el valor estará disponible en la variable info.dValue. En ese momento, podremos comprobar cómo está el indicador de control. En la línea 40, verificamos si ya se ha inicializado. Como es la primera vez, obtendremos como respuesta que el indicador no se ha inicializado. Luego, en las líneas 42 y 43, asignamos el valor que se va a transferir al indicador y enviamos una solicitud a MetaTrader 5 para que se genere un evento personalizado en el gráfico. Este evento se muestra en la línea 44, donde transferiremos un mensaje al indicador de control para inicializarlo.

En cualquier otro momento, verificaremos si el estado es de reproducción o de pausa. Si es así, la línea 45 permitirá que almacenemos los valores ajustados por el usuario en el indicador. Así, cuando el usuario cambie el marco temporal, la prueba de la línea 40 dará un valor verdadero y los valores memorizados aquí en el servicio se notificarán nuevamente al indicador, haciendo su uso viable, incluso si se modifica el marco temporal, pues el indicador siempre sabrá cómo inicializarse correctamente.

Al final, cerramos el gráfico usando la línea 52 e informamos que el servicio ha sido finalizado. Esto en la línea 53.

En el video a continuación, puedes ver el sistema en funcionamiento, en caso de que no quieras experimentar con él. Además, dejaré los ejecutables adjuntos para que puedas ver cómo funciona todo en la práctica.



Video de demostración.


Conclusión

En este artículo empezamos a dar forma a lo que realmente se verá en los próximos artículos. Sé que el contenido es bastante denso, así que estudia con calma lo que muestro, porque de ahora en adelante la cosa solo se irá complicando cada vez más.

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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Aprendizaje automático y ciencia de datos (Parte 18): Potencie sus modelos de IA con AdaBoost Aprendizaje automático y ciencia de datos (Parte 18): Potencie sus modelos de IA con AdaBoost
AdaBoost, un potente algoritmo de refuerzo diseñado para elevar el rendimiento de sus modelos de IA. AdaBoost, abreviatura de Adaptive Boosting (refuerzo adaptativo), es una sofisticada técnica de aprendizaje por conjuntos que integra a la perfección los aprendices débiles, potenciando su fuerza predictiva colectiva.
Desarrollo de un sistema de repetición (Parte 54): El nacimiento del primer módulo Desarrollo de un sistema de repetición (Parte 54): El nacimiento del primer módulo
En este artículo, veremos cómo construir el primero de los módulos, realmente funcional, para ser utilizado en el sistema de repetición/simulador. Además de tener como propósito general servir para otras cosas también. El módulo que se construirá aquí será el del indicador de mouse.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Desarrollo de un sistema de repetición (Parte 53): Esto complica las cosas (V) Desarrollo de un sistema de repetición (Parte 53): Esto complica las cosas (V)
En este artículo, presentaré un tema muy importante, que pocos comprenden realmente: Eventos personalizados. Peligros. Ventajas y fallos causados por tales elementos. Este tema es clave para quienes desean convertirse en programadores profesionales en MQL5 o en cualquier otro tipo de lenguaje. Por ello, nos centraremos en MQL5 y MetaTrader 5.