English Русский Português
preview
Desarrollo de un sistema de repetición (Parte 35): Haciendo retoques (I)

Desarrollo de un sistema de repetición (Parte 35): Haciendo retoques (I)

MetaTrader 5Ejemplos | 4 abril 2024, 09:42
135 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior "Desarrollo de un sistema de repetición (Parte 34): Sistema de órdenes (III), informé de que el sistema tenía algunos fallos bastante extraños e incluso desconcertantes. La razón es que esos fallos se producían por algún tipo de interacción dentro del sistema. A pesar de los intentos de comprender la razón de algunos de los fallos, para ponerles fin. Todos ellos se vieron frustrados, ya que algunos no tenían sentido. Cuando usamos punteros o recursión en C / C++, y el programa empieza a fallar. Uno de los primeros pasos es comprobar estos mecanismos. Aquí en MQL5, esto no sucede de la misma manera que en C / C++. Pero después de hacer unas pequeñas modificaciones, se subsanó uno de los defectos. Aunque no creo que sea una solución tan elegante. De hecho, hizo desaparecer por completo uno de los defectos.

Aún así tendremos que hacer una modificación algo más radical en el código. Para subsanar por completo los fallos que afectan al sistema. Éstos deben haber estado ahí hace mucho tiempo. Porque algunos tipos de interacción no se producían dentro del sistema. Esto de alguna manera muy específica. Pero en cuanto empezaron a producirse estas interacciones, los fallos que habían estado ahí, pero que habían pasado desapercibidos, empezaron a ser notados durante el uso del programa.

Los fallos presentes no afectan negativamente al sistema. Pero no te permiten trabajar de una forma realmente adecuada. Además de confundir al usuario. Lo que hace que la experiencia del programa sea bastante desagradable e inaceptable. El primero de estos fallos es relativamente sencillo de solucionar. Así que vamos al tema donde explicaré cómo solucionarlo.


Resolver la indicación de servicio ocupado.

El primero de los fallos es el más sencillo de corregir. Él no hace pulsar CTRL o SHIFT para posicionar las órdenes. Aparece la indicación de que el servicio está ocupado. Esto significa que durante el proceso de reproducción, aunque el sistema funcione con normalidad, se indica que el servicio está realizando alguna otra tarea. Esta tarea supone un desplazamiento en el tiempo. Que consiste en que las barras se crean antes de poder hacer cualquier tipo de análisis o estudio en el gráfico de repetición/simulador. Aunque este defecto no es extremadamente perjudicial. Hace que la experiencia de utilizar la repetición/simulador sea bastante desagradable. Porque nos confunde más de lo que nos informa.

Algunas personas podrían pensar que lo mejor sería eliminar la indicación de servicio ocupado. Ya que hace tiempo que no utilizamos la visualización de la creación de barras durante el desplazamiento en el tiempo. Pero hacer esto no resuelve realmente el problema. Con ello sólo se barrería y escondería el problema bajo la alfombra. Pero la solución es bastante sencilla y eficiente. Todo ello frente al hecho de que podamos guardar las cosas para que en algún momento del futuro podamos volver al sistema de visualización, donde se puede ver la creación de las barras durante el paso del tiempo. Igual que antes de que hubiéramos desactivado esta función del servicio de repetición/simulación. Así que, para resolver el problema, tenemos que hacer algunos cambios. Empezaremos con el siguiente código:

int OnInit()
{
#define macro_INIT_FAILED { ChartIndicatorDelete(m_id, 0, def_ShortName); return INIT_FAILED; }
        u_Interprocess Info;
        ulong ul = 1;

        m_id = ChartID();
        ul <<= def_BitShift;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
        if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED;
        Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics);
        if (Info.u_Value.IdGraphic != m_id) macro_INIT_FAILED;
        if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED;
        IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device");
        Info.u_Value.IdGraphic |= ul;
        GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 
        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
        Control.Init(Info.s_Infos.isPlay);
        
        return INIT_SUCCEEDED;
        
#undef macro_INIT_FAILED
}

Esta primera modificación debe hacerse en el archivo del indicador. Arriba puedes ver dónde se está produciendo exactamente el primero de los cambios. La parte señalada se ha eliminado y en su lugar tenemos este nuevo código. Fíjate bien, porque el único cambio que realmente había que hacer aquí era en el valor del parámetro lparam que hay que pasar a la función EventChartCustom. Por extraño que parezca, este sencillo cambio ya está marcando la diferencia.

En el mismo archivo, pero en una función diferente, tendremos que hacer algo muy parecido. Esto se puede ver justo debajo:

int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
  static bool bWait = false;
  u_Interprocess Info;
        
  Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
  if (!bWait)
  {
     if (Info.s_Infos.isWait)
     {
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 0, 0, "");
        EventChartCustom(m_id, C_Controls::ev_WaitOn, 1, 0, "");
        bWait = true;
     }
  }else if (!Info.s_Infos.isWait)
  {
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 0, Info.u_Value.df_Value, "");
     EventChartCustom(m_id, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, "");
     bWait = false;
  }
        
  return rates_total;
}

Tal y como ocurría en el código OnInit. Aquí las partes señaladas son eliminadas, y en su lugar tenemos nuevo código. Pero la única diferencia entre el código original y el nuevo es el valor del parámetro lparam. Una vez más, ocurrió lo mismo al llamar a la función EventChartCustom.

Puede que estés pensando: Pero, ¿por qué sucede esto? ¿Funcionará de verdad? De hecho, hacer estos cambios aquí en el código del indicador no resolverá el problema. Para resolver realmente el problema, tenemos que ir a la clase C_Control y añadir una pequeña prueba a la función de gestión de mensajes. Así que puedes ver el nuevo código a continuación:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
//... Restante do código ...
  }
}

Aquí estoy capturando sólo el fragmento que realmente necesitamos para entender la solución al problema. Observa que se ha añadido una pequeña prueba a ambos eventos personalizados. Y el hecho de que cuando se produce una interacción con el usuario dispara un evento personalizado en el programa provoca que estas pruebas sólo permitan que se ejecute el evento personalizado. Estos eventos se activan precisamente en el código del indicador de control. Por cualquier otro motivo, la cantidad que llegará aquí al manipulador será diferente de la que usted esperaba. Esto considerando el valor del parámetro lparam. Este tipo de cosas volverán a ocurrir en el futuro. Cuando explique cómo hacer uso de algo bastante curioso en la plataforma MetaTrader 5. Allí profundizaré en la explicación de por qué se produce este fallo. Pero por el momento el motivo es extraño de todos modos.

Pero espera un momento. ¡No tiene el menor sentido! De hecho, al principio tampoco entendía por qué. Pero de alguna manera, cuando pulsamos la tecla SHIFT o CTRL, para utilizar el sistema de órdenes, que se está desarrollando. La plataforma está generando un evento que desencadena los dos eventos anteriores. Pero principalmente CHARTEVENT_CUSTOM + C_Controls::ev_WaitOnque hace que la imagen del servicio ocupado aparezca en el gráfico. Y tan pronto como sueltes las teclas SHIFT o CTRL, un evento CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff es activado y hace que todo vuelva a la normalidad.

Solucionado este problema, podemos pasar al siguiente. Este segundo problema, sin embargo, es bastante más complejo de solucionar. Tendrás que verlo en un tema nuevo para no confundirte. Así que veamos el siguiente tema para comprender la situación real que hay que resolver.


Hacer una reutilización más profunda de la clase C_Mouse.

La forma de remediar el siguiente fallo, que se produce cuando estamos realizando un estudio en el que el sistema puede acabar moviendo el indicador de posición sin que el usuario se dé cuenta, implica una edición de código mucho más profunda. Muchos podrían pensar que basta con hacer una prueba para comprobar que el usuario realmente intenta mover el pin de desplazamiento. Este pin forma parte integrante del indicador de control y sirve para que el usuario sepa en qué punto se encuentra la repetición o simulación. Pero una simple prueba no basta para resolver esta situación. Tenemos que ir un poco más allá de esta simple prueba. De hecho, vamos a hacer algo mucho más complicado al principio, pero resolverá el problema de forma mucho más definitiva, además de permitirnos hacer otras cosas después.

Vamos a utilizar la clase C_Mouse, que se utiliza inicialmente en el Expert Advisor, dentro del indicador de control también. Pero hacer esto no es tan sencillo como podría pensarse. Habrá que introducir cambios drásticos en varios puntos del código. Esto es para que la clase C_Mouse pueda trabajar en perfecta armonía, tanto en el indicador de control como en el Expert Advisor. Todo ello sin generar ningún tipo de conflicto o problema. Pero haciendo esto, abriremos la puerta que nos permitirá hacer algo más.

Empecemos por ver la clase C_Mouse y lo que ha cambiado en la misma. Para explicar los cambios, tenemos que ver lo que realmente se ha añadido al código de esta clase. Lo primero que se ha hecho se refiere a las variables globales privadas. Como puede verse en el fragmento siguiente:

struct st_Mem
{
   bool     CrossHair,
            IsFull;
   datetime dt;
}m_Mem;

Esta variable, que se ha añadido y está resaltada, se utilizará para realizar una prueba dentro de la clase. Su prioridad es definir cómo se ejecutará realmente la clase. Esto es en relación con los objetos con los que la clase tendrá que trabajar durante su periodo de vida. Una vez declarada esta variable, tenemos que empezar con buen pie. Para ello, utilizaremos el constructor de la clase. Pero ahora llega la primera complicación. ¿Cómo sabes si se debe utilizar el sistema en modo FULL o no? Detalle: al utilizar el modo FULL, en realidad estamos haciendo uso de los objetos gráficos. Estos objetos serán utilizados por el Expert Advisor. Cuando no estamos utilizando la clase dentro del Expert Advisor, tales objetos NO deben ser creados. Pero, ¡¿cómo podemos decirle a la clase si estamos usando un Expert Advisor o algo más?! Hay varias formas de hacerlo. Y seguro lo haremos, pero necesitaremos usar uno que se haga a través del constructor de la clase. De este modo, se evita un gran número de pruebas adicionales posteriores y los riesgos que conlleva el uso indebido de la clase. Por lo tanto, el constructor será modificado y se verá como se muestra a continuación:

C_Mouse(C_Terminal *arg, color corH = clrNONE, color corP = clrNONE, color corN = clrNONE)
{
   Terminal = arg;
   if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   if (_LastError != ERR_SUCCESS) return;
   m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
   ZeroMemory(m_Info);
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
}

Ahora presta mucha atención a lo que se está haciendo aquí. El hecho de que estemos definiendo estos colores con un valor por defecto aquí en la declaración del constructor nos permite sobrecargar el constructor. Esta sobrecarga significará que en realidad no necesitamos crear otro código constructor para utilizar esta misma clase C_Mouse. Esto es para diferenciar entre su uso en el indicador de control o en el Expert Advisor. Si esta sobrecarga no se hiciera, o fuera posible, tendríamos que crear otro constructor sólo para decirle a la clase si se va a utilizar en modo FULL o NO. En este punto del código del constructor, definimos el valor de la variable. Al mismo tiempo, ya estamos haciendo el primer uso de su valor. Si estamos en modo FULL, crearemos la línea de precio que se utilizará en el gráfico. De lo contrario, esta línea no se creará.

El código del destructor también ha sufrido un pequeño cambio. Puedes verlo a continuación:

~C_Mouse()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName);
}

Si la construcción de la clase falla porque el puntero a la clase C_Terminal no se ha inicializado correctamente, no podremos utilizar el puntero en el destructor. Para evitar que se utilice un puntero no válido, realizamos esta prueba en el destructor de la clase. Esta fue la parte fácil de la codificación inicial. Ahora vamos a echar un vistazo rápido al código antiguo de la clase, sólo para ver lo que tenemos que cambiar.

void CreateStudy(void)
{
   if (m_Mem.IsFull)
   {
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
      CreateObjectInfo(0, 0, def_NameObjectStudy);
   }
   m_Info.Study = eStudyCreate;
}
//+------------------------------------------------------------------+
void ExecuteStudy(const double memPrice)
{
   double v1 = GetInfoMouse().Position.Price - memPrice;
   int w, h;
                                
   if (!CheckClick(eClickLeft))
   {
      m_Info.Study = eStudyNull;
      ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
      if (m_Mem.IsFull) ObjectsDeleteAll(def_InfoTerminal.ID, def_MousePrefixName + "T");
   }else if (m_Mem.IsFull)
   {
      string sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ",
      MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1/ memPrice) * 100.0)));
      GetDimensionText(sz1, w, h);
      ObjectSetString(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_TEXT, sz1);                                                                                                                           
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XSIZE, w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YSIZE, h);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X - w);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y - (v1 < 0 ? 1 : h));                            
      ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
      ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
   }
   m_Info.Data.ButtonStatus = eKeyNull;
}

Estos dos procedimientos anteriores se han modificado de forma que resulten muy fáciles y sencillos de entender. Observe que se ha añadido una pequeña prueba a ambos procedimientos para poder crear y manipular los objetos. Sólo si estamos utilizando la clase en su modo FULL, tendrán lugar realmente tales manipulaciones. Pero también tenemos otro procedimiento dentro de la clase C_Mouse. Esto es realmente importante para nosotros, ya que es el procedimiento mediante el que interactuaremos con la clase. Su código puede verse en su totalidad a continuación:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   int w = 0;
   static double memPrice = 0;
                                
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + ev_HideMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
         break;
      case (CHARTEVENT_CUSTOM + ev_ShowMouse):
         if (m_Mem.IsFull) ObjectSetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         ChartXYToTimePrice(def_InfoTerminal.ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
         if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = def_AcessTerminal.AdjustPrice(m_Info.Data.Position.Price));
         m_Info.Data.Position.dt = def_AcessTerminal.AdjustTime(m_Info.Data.Position.dt);
         ChartTimePriceToXY(def_InfoTerminal.ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
         if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
         m_Info.Data.ButtonStatus = (uint) sparam;
         if (CheckClick(eClickMiddle))
            if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(def_InfoTerminal.ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
                  if (m_Mem.IsFull) ObjectMove(def_InfoTerminal.ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
                  m_Info.Study = eStudyExecute;
               }
         if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
         m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
         break;
   }
}

Al igual que los procedimientos anteriores, éste también sufrió el mismo tipo de modificación. En realidad, lo único que se ha hecho es añadir pruebas para saber si se deben manipular o no los objetos del gráfico. Podría pensarse que estas pruebas son innecesarias, ya que en algunos casos los objetos no estarían en el gráfico y, si lo estuvieran, serían manipulados de la misma manera. Esto se debe a que la plataforma MetaTrader 5 no crearía objetos duplicados. Pero quiero que pienses en el hecho de que cuando haces estas manipulaciones en los objetos, no sólo las haces una vez. El hecho de que el Expert Advisor y el indicador de control estén utilizando la misma clase, pero sin saber que ambos la están utilizando al mismo tiempo, obliga a la plataforma MetaTrader 5 a realizar una doble llamada a los manejadores de objetos. Esto no es realmente un problema, pero hay que recordar que estamos utilizando el sistema para realizar una simulación o repetición del mercado. Y desde el principio de este proyecto, he reiterado varias veces que nuestro mayor problema es conseguir que la simulación o repetición sea lo más parecida posible a lo que ocurre en un mercado real. Esto no se refiere a la simple creación de las barras, sino más bien al tiempo que hay que emplear para crear estas barras. Hacer que MetaTrader 5 realice dobles llamadas a objetos que podrían presentarse en una sola llamada consume un tiempo precioso. No importa qué procesador o equipo utilices. Si empiezas a perder milisegundos o incluso nanosegundos a causa de códigos duplicados en términos de llamadas, pronto acabarás perdiendo segundos o retrasando demasiado la creación de las barras. Así que sí, debemos probar las cosas para evitar que MetaTrader 5 tenga que hacer un trabajo extra innecesario con el fin de mostrar los objetos en el gráfico.

Bien, con las modificaciones al código de la clase C_Mouse hechas, no necesitamos preocuparnos por el código del Expert Advisor. Aunque la estructura de directorios ha cambiado, esto solo afecta al código de inclusión. Así que no veo la necesidad de entrar en esto en detalle aquí en el artículo. El hecho de que el código del Expert Advisor no sufriera ninguna desviación en su implementación es un pequeño alivio. Pero esto es solo temporal, ya que el código del indicador de control, que se utiliza para controlar la repetición/simulador, sufrirá algunos cambios mucho más profundos que merecen ser explicados en detalle y con calma. Así que vamos a empezar un nuevo tema.


Modificar del indicador de control

Para realizar este cambio en el indicador de control, de hecho, empezaremos trabajando en la clase que utiliza. Me refiero a la clase C_Control. En realidad, las modificaciones del código comienzan en el siguiente punto, que se muestra en el fragmento siguiente:

#include "..\Auxiliar\C_Terminal.mqh"
#include "..\Auxiliar\C_Mouse.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Controls : protected C_Mouse
{
        protected:
                enum EventCustom {ev_WaitOn, ev_WaitOff};
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                bool    m_bWait;
                struct st_00
                {
                        string  szBtnLeft,
                                szBtnRight,
                                szBtnPin,
                                szBarSlider,
                                szBarSliderBlock;
                        int     posPinSlider,
                                posY,
                                Minimal;
                }m_Slider;
                C_Terminal *Terminal;

Tú puedes ver de inmediato que el código tiene ahora dos llamadas para incluir los archivos, tanto la clase C_Terminal como la clase C_Mouse. Pero, ¿por qué no utilizar la clase C_Study, que hereda de la clase C_Mouse? La razón es que no vamos a hacer que el indicador de control haga o genere estudios. Esto es trabajo para el Expert Advisor, al menos en este punto de la implementación, y no por el indicador de control. Por eso vamos a utilizar la clase C_Mouse. Observa que la clase C_Control hereda de la clase C_Mouse. Aunque esta herencia tenga lugar, el indicador de control no se beneficiará realmente de esta herencia, al menos no directamente. Por este motivo, dicha herencia podría hacerse incluso en privado, pero suelo tratar este tipo de herencia como protegida. Y como último punto a destacar en estas declaraciones, tenemos el puntero que utilizará la clase C_Terminal.

NOTA: En el código original de la clase C_Control, existía una variable global privada que daba acceso al índice de la ventana del gráfico. Este índice ya no existirá en esta versión. La razón es que vamos a utilizar la clase C_Terminal para hacer este trabajo, que antes se hacía a través de un índice interno de la clase C_Control.

Debido a este detalle sobre el índice, se han eliminado todos los puntos en los que se hacía referencia a ella. Allí donde era realmente necesario, se sustituyó por la definición que nos permite acceder al índice a través de la clase C_Terminal. Bien, veamos ahora el código del constructor de la clase. Esto se puede ver a continuación:

C_Controls(C_Terminal *arg)
          :C_Mouse(arg),
           m_bWait(false)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      m_szBtnPlay             = NULL;
      m_Slider.szBarSlider    = NULL;
      m_Slider.szBtnPin       = NULL;
      m_Slider.szBtnLeft      = NULL;
      m_Slider.szBtnRight     = NULL;
   }

Ahora el constructor recibirá un parámetro, que es un puntero a la clase C_Terminal. Se pasa a la clase C_Mouse. Pero tenga en cuenta que ahora el C_Mouse, NO se utilizará en modo FULL. Entonces los objetos de la clase C_Mouse no se crearán realmente. Esto se debe a que sólo servirá para apoyarnos en el modo de reutilización del código. Aun así, hemos comprobado aquí si realmente merece la pena utilizar el puntero. Esto es importante para que no utilicemos algo que apunte a una región de memoria no válida o desconocida. También tenemos el destructor de la clase, que ha recibido una ligera modificación, como se puede ver a continuación:

~C_Controls()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName);
}

Esta prueba evita que intentemos utilizar un puntero no válido, ya que el constructor puede haber fallado, pero el destructor no lo sabe. Así que haciendo esta prueba, garantizamos que el destructor también sabrá por qué falló el constructor. Aquí es cuando falla, ya que puede ser que se esté llamando al destructor simplemente terminando el uso de la clase C_Controls. Como ahora estamos utilizando un sistema, algunas cosas se inicializan fuera de esta clase, por lo que podemos hacer un cambio adicional en el código original.

void Init(const bool state)
{
   if (m_szBtnPlay != NULL) return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, true);
   CreateBtnPlayPause(state);
   GlobalVariableTemp(def_GlobalVariableReplay);
   if (!state) CreteCtrlSlider();
   ChartRedraw();
}

Las líneas señaladas arriba se han eliminado porque las cosas que están estableciendo ya se han hecho en otros sitios. Esto se debe a que la clase C_Mouse activará el movimiento del ratón y la clase C_Terminal indicará a la plataforma MetaTrader 5 que queremos recibir una alerta si se elimina un objeto del gráfico. Por estas razones, se han eliminado estas líneas, ya que si permanecieran en el código, podríamos tener un comportamiento extraño del código en determinadas situaciones. Recuerda: En ningún caso debemos suponer o duplicar código. Hacer esto no constituye un error, pero es un error, ya que dificulta la correcta ejecución del código en algunos escenarios, y dificulta enormemente su mantenimiento en el tiempo.

El siguiente código a modificar es el manejador de mensajes de la clase. Esto se puede ver a continuación:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   u_Interprocess Info;
   static int six = -1, sps;
   int x, y, px1, px2;
                                
   C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
   switch (id)
   {
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn):
         if (lparam == 0) break;
         m_bWait = true;
         CreateBtnPlayPause(true);
         break;
      case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff):
         if (lparam == 0) break;
         m_bWait = false;
         Info.u_Value.df_Value = dparam;
         CreateBtnPlayPause(Info.s_Infos.isPlay);
         break;
      case CHARTEVENT_OBJECT_DELETE:
         if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName)
         {
            if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider)
            {
               RemoveCtrlSlider();
               CreteCtrlSlider();
            }else
            {
               Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
               CreateBtnPlayPause(Info.s_Infos.isPlay);
            }
            ChartRedraw();
         }
         break;
      case CHARTEVENT_OBJECT_CLICK:
         if (m_bWait) break;
         if (sparam == m_szBtnPlay)
         {
            Info.s_Infos.isPlay = (bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE);
            if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
            {
               RemoveCtrlSlider();
               m_Slider.szBtnPin = NULL;
            }
            Info.s_Infos.iPosShift = (ushort) m_Slider.posPinSlider;
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            ChartRedraw();
         }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);
         else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         if (GetInfoMouse().ExecStudy) return;
         if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL))
         {
            x = GetInfoMouse().Position.X;
            y = GetInfoMouse().Position.Y;
            px1 = m_Slider.posPinSlider + def_PosXObjects + 86;
            px2 = m_Slider.posPinSlider + def_PosXObjects + 114;
            if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1))
            {
               six = x;
               sps = m_Slider.posPinSlider;
               ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
            }
            if (six > 0) PositionPinSlider(sps + x - six);
         }else if (six > 0)
         {
            six = -1;
            ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
         }
         break;
   }
}

Este código de aquí ha sufrido algunos cambios con respecto a lo que era antes. Lo que estás viendo es la versión final. Así será más fácil explicar lo que ocurre aquí. Como indicador de control, necesita saber cómo se comporta el ratón. Usar sólo el evento CHARTEVENT_MOUSE_MOVE para esto no es realmente suficiente. Necesitamos que la clase C_Mouse haga su trabajo. Recuerde, aunque el Expert Advisor utiliza la clase C_Mouse, al igual que el indicador, los dos no comparten información sobre lo que el ratón está haciendo.

Pensé en hacer este tipo de intercambio, que es realmente posible. Pero tenemos algunos problemas para hacerlo.

  1. Si comparte los datos del ratón entre el Expert Advisor y el Indicador de Control, utilizando algún tipo de artificio de la plataforma MetaTrader 5, puede tener problemas cuando necesite utilizar el Expert Advisor fuera del sistema de repetición/simulador. O puede tener problemas para que el indicador de control entienda realmente lo que está haciendo el ratón.
  2. Otra posibilidad sería utilizar una memoria compartida, a través de una DLL. Pero hacerlo crearía una dependencia de la plataforma o del servicio de repetición/simulador. Y la verdad es que no tengo ningún interés en crear esa dependencia por el momento. De esta forma, el uso de memoria compartida para que MetaTrader 5 no tenga que gestionar la clase C_Mouse de la forma en que lo hace ya no es realmente válido.

Estas razones pueden no parecer válidas, pero quiero aprovechar al máximo las capacidades del lenguaje MQL5, así como la plataforma MetaTrader 5, con el fin de construir un sistema de repetición/simulador, demostrando que podemos hacer mucho más de lo que muchos creen posible en la plataforma o en el idioma. Debido a esto, necesitamos hacer que la clase C_Mouse actualice los datos para cualquier cosa relacionada con el ratón aquí también. Esto se hace utilizando esta llamada desde aquí. Como de hecho vamos a manejar eventos de ratón, a través de CHARTEVENT_MOUSE_MOVE, podemos pasar a este evento específico.

Lo primero que hacemos es comprobar si el usuario está realizando algún tipo de estudio sobre el gráfico. Si este es el caso, todo lo relacionado con el manejo de eventos de ratón en la clase C_Control debe ser ignorado. A partir de ahora, no manejaremos los eventos del ratón localmente. En su lugar, preguntaremos a la clase C_Mouse qué ha ocurrido y, a continuación, tomaremos las decisiones y medidas adecuadas para cumplir los deseos del usuario. A continuación, comprobamos que el usuario hace clic con el botón izquierdo y que el objeto pin está presente en el gráfico (modo pausa). Si es cierto, comprobamos la posición en la que se ha producido el clic. Si estaba en el objeto pin, responderá adecuadamente siempre que se pulse el botón izquierdo. De este modo, el usuario puede arrastrar el objeto pin de un lado a otro, de la misma forma que se hacía antes.

Así que todo lo que realmente teníamos que hacer era añadir una forma para que la clase C_Control sepa si el usuario está ejecutando algún tipo de estudio o no. Este tipo de cosas podrían incluso hacerse de otra manera, pero como se decía al principio del artículo: Hacerlo así nos da algunas ventajas, aunque de momento no sean tan evidentes.


Conclusión

En este artículo, he demostrado cómo se puede actuar para utilizar clases o métodos que fueron desarrollados originalmente para su uso en un Expert Advisor en un programa diferente, en este caso un indicador. Todo el trabajo realizado aquí nos dará una mejor experiencia al utilizar el sistema de repetición/simulador. Para que un usuario que esté desarrollando una técnica, o quiera probar una determinada forma de operar, pueda hacerlo utilizando el sistema que se está desarrollando en esta secuencia. Hay un último problema, que se está subsanando actualmente. Pero esto se verá en el próximo artículo de esta serie. Porque para hacer la corrección, tendrás que cambiar algunas cosas y añadir otras. Y como todo el contenido visto en este artículo de aquí ya puede ser muy complicado para los que están empezando a aprender programación. No voy a aumentar el nivel de complejidad así como así.

En el anexo, tendrá acceso tanto al código completo del sistema. Cuánto de tres grupos de archivos para que usted pueda probar, y probar el sistema. Y así puedo aprender un poco más sobre cómo se ha desarrollado.


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

Archivos adjuntos |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Redes neuronales: así de sencillo (Parte 63): Entrenamiento previo del Transformador de decisiones no supervisado (PDT) Redes neuronales: así de sencillo (Parte 63): Entrenamiento previo del Transformador de decisiones no supervisado (PDT)
Continuamos nuestra análisis de la familia de métodos del Transformador de decisiones. En artículos anteriores ya hemos observado que entrenar el transformador subyacente en la arquitectura de estos métodos supone todo un reto y requiere una gran cantidad de datos de entrenamiento marcados. En este artículo, analizaremos un algoritmo para utilizar trayectorias no marcadas para el entrenamiento previo de modelos.
Desarrollo de un sistema de repetición (Parte 34): Sistema de órdenes (III) Desarrollo de un sistema de repetición (Parte 34): Sistema de órdenes (III)
En este artículo concluiremos la primera fase de la construcción. Aunque será algo relativamente rápido, explicaré detalles que quizás no se comentaron anteriormente. Pero aquí explicaré algunas cosas que mucha gente no entiende por qué son como son. Uno de estos casos es el del ratón. ¡¡¡¿Sabes por qué tienes que pulsar la tecla Shift o Ctrl en tu teclado?!!!
Desarrollo de un sistema de repetición (Parte 36): Haciendo retoques (II) Desarrollo de un sistema de repetición (Parte 36): Haciendo retoques (II)
Una de las cosas que más nos puede complicar la vida como programadores es el hecho de suponer cosas. En este artículo, te mostraré los peligros de hacer suposiciones: tanto en la parte de programación MQL5, donde se asume que un tipo tendrá un tamaño determinado, como cuando se utiliza MetaTrader 5, donde se asume que los diferentes servidores funcionan de la misma manera.
Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 1): Como usar RestAPIs en MQL5 Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 1): Como usar RestAPIs en MQL5
Este artículo aborda la importancia de las APIs (application programming interface) en la comunicación entre diferentes aplicaciones y sistemas de software. En él, se destaca el papel de las API a la hora de simplificar la interacción entre aplicaciones, ya que les permiten compartir datos y funcionalidades de forma eficiente.