English Русский 中文 Deutsch 日本語 Português
preview
Mejore sus gráficos comerciales con una GUI interactiva basada en MQL5 (Parte II): Interfaz móvil (II)

Mejore sus gráficos comerciales con una GUI interactiva basada en MQL5 (Parte II): Interfaz móvil (II)

MetaTrader 5Sistemas comerciales | 1 noviembre 2023, 15:39
1 393 0
Kailash Bai Mina
Kailash Bai Mina

Introducción

En la primera parte vimos cómo crear un panel móvil simple. Hoy describiremos aquí una forma más eficaz de lograr el mismo objetivo, adecuada para asesores/indicadores completos.

Por ejemplo, si quisiéramos tener dos paneles móviles en la pantalla, necesitaríamos duplicar el código existente y crear seis variables globales adicionales, cada una con un nombre diferente. Si necesitamos tres paneles móviles, la complejidad del código aumentará significativamente y se volverá mucho más difícil de gestionar. Resulta evidente que necesitamos un enfoque más racional.

Por fortuna, podemos recurrir a archivos .mqh para facilitar este proceso.

En el artículo de hoy abarcaremos los siguientes puntos:

  1. Concepto de clases
  2. Creamos un panel usando un archivo .mqh.
  3. Instalamos dos paneles en un gráfico utilizando un archivo .mqh.


Concepto de clases

En primer lugar, echaremos un vistazo al concepto de clases. El tema es amplio y complejo, pero por ahora solo necesitaremos lo básico.

En términos simples, una clase es un tipo de datos complejo, similar a int, string y otros, pero algo más complejo.

Existen muchas definiciones de las clases, aunque esencialmente se pueden considerar como grupos de código. ¿Y qué tipo de código es este? Con frecuencia se trata de una colección de funciones (conocidas como métodos) y variables. Algunos podrían decir que es una definición vaga o imprecisa, pero no estamos en un examen. Nuestro objetivo principal consiste en usar el poder de las clases para hacer que la escritura de código resulte más manejable y eficiente, por lo que aquí no juega un papel crucial la disposición de definiciones estrictas.

Básicamente, las clases son un conjunto de funciones y variables que podemos usar para nuestros propios objetivos.

Esta definición nos lleva de forma natural a cuatro preguntas fundamentales:

  1. ¿Dónde las creamos?
  2. ¿Cómo las declaramos?
  3. ¿Cómo las escribimos?
  4. ¿Cómo las usamos?

La respuesta a la pregunta de por qué necesitamos clases es bastante simple: las clases simplifican el proceso de escritura y gestión del código.

  1. ¿Dónde las creamos?

    Nuestra elección del tipo de archivo para crear las clases, ya sea .mq5 o .mqh, es flexible, aunque normalmente seleccionamos archivos .mqh individuales.

    La diferencia entre crear clases en .mq5 y .mqh resulta bastante notable. Si desarrollamos nuestra clases en un archivo .mqh, deberemos importarlo a .mq5. Esto se debe a que la creación de un asesor/indicador está destinada exclusivamente a archivos .mq5. Sin embargo, si instalamos la clase directamente en un archivo .mq5, no será necesario ningún proceso de importación.

    Generalmente preferimos archivos .mqh separados porque hacen el código más manejable. El proceso de importación es sencillo y requiere apenas una línea de código. Aquí usaremos un archivo .mqh separado.


  2. ¿Cómo las declaramos?

    Declarar una clase es un tarea bastante simple. A continuación le mostramos un ejemplo de una declaración de clase vacía simple:

    class YourClassName
      {
      };
    En el fragmento de código anterior, YourClassName es un placeholder. Deberemos reemplazar YourClassName con el nombre real que deseemos asignar a nuestra clase.



  3. ¿Cómo las escribimos?

    Para responder a esta pregunta, comenzaremos analizando las variables y luego pasaremos a las funciones.

    Digamos que queremos declarar dos variables: una de tipo int y otra de tipo bool. Podemos hacerlo así:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1;
       bool var2;
      };
    //+------------------------------------------------------------------+
    

    No podemos asignar valores a estas variables directamente en la declaración de clase. Por ejemplo, el siguiente código generará un error:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1 = "int var";
       bool var2 = true;
      };
    //+------------------------------------------------------------------+
    
    El error será:
    '=' - illegal assignment use
    '=' - illegal assignment use

    En escenarios donde nuestra lógica depende de los valores iniciales de las variables, podemos utilizar lo que se llama constructor. También tenemos su antípoda: el destructor.

    Los constructores y destructores son funciones especiales que siempre están vinculadas con una clase, ya las declaremos explícitamente o no. Si no las declaramos, implícitamente se considerarán funciones vacías. Un constructor se ejecuta al declararse una instancia de la clase, mientras que un destructor se ejecuta cuando una instancia de la clase se sale del alcance. En MQL5 no hay forma de eliminar explícitamente una instancia de clase.

    Por ejemplo, la función OnInit() en nuestro código se comporta como un constructor, mientras que la función OnDeinit() se comporta como un destructor: la clase aquí está oculta en segundo plano para garantizar la simplicidad. Este comportamiento es común en muchos lenguajes, como Java, que siempre incluye una clase por defecto.

    A continuación veremos qué significa "instancia".

    Por ahora, es importante comprender que podemos usar constructores para asignar valores iniciales a nuestras variables. No necesitaremos un destructor aún, pero definitivamente lo veremos en las próximas partes de la serie.

    Le recomiendo encarecidamente utilizar el constructor.

    Aunque algunos programadores ignoran las advertencias del compilador y asumen que a las variables se les asignará implícitamente un valor por defecto a menos que las definan explícitamente, este enfoque no es completamente correcto ni completamente incorrecto.

    Esta suposición se debe a que en muchos lenguajes de programación (incluso en MQL4), las variables sin una definición explícita por defecto toman algún valor sin provocar inconsistencias en el código. No obstante, en MQL5 podemos encontrar inconsistencias en nuestro código si no definimos variables explícitamente.

    Estos son los valores por defecto sugeridos para los tipos de datos más utilizados:

    Tipo Código Valor predeterminado asumido
     int  int test; 0
     double  double test; 0.0
     bool  bool test; false
     string  string test; NULL
     datetime  datetime test;   1970.01.01 00:00:00

    El valor predeterminado asumido es el valor que veremos al imprimir una variable no inicializada. Si probamos valores utilizando una declaración if, algunos se comportarán como se esperaba, mientras que otros no, lo cual puede generar problemas.

    Nuestro script de prueba se verá así:

    void OnStart()
      {
       type test;
       if(test == presumedDefaultValue)
         {
          Alert("Yes, This is the default value of the test variable.");
         }
       else
         {
          Alert("No, This is NOT the default value of the test variable.");
         }
      }

    Reemplazaremos type con el tipo de variable y supuestoDefaultValue con el valor que esperamos que sea el valor predeterminado.

    Veremos que para bool y string todo funciona bien y aparecerá el mensaje "Yes, This is the default value of the test variable" ("Sí, este es el valor predeterminado de la variable de prueba"). Sin embargo, para int, double y datetime las cosas no resultarán tan simples. Recibiremos el mensaje "No, This is NOT the default value of the test variable" ("No, este NO es el valor predeterminado de la variable de prueba"). Este resultado inesperado podría causar problemas lógicos.

    Ahora que entendemos la importancia de los constructores, vamos a ver cómo crear uno:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name();
                        ~name();
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+
    

    Aquí hemos incluido en la clase campos públicos y privados, aunque el campo privado está actualmente vacío. Es muy importante declarar el constructor con el modificador de acceso público para poder usarlo en otros archivos. Para una mejor comprensión, vamos a analizar los modificadores de acceso (o especificadores de acceso).

    Los modificadores de acceso definen cómo el compilador puede acceder a las variables, los miembros de estructura o las clases.

    Modificadores de acceso Descripción
     público  Permite el acceso sin restricciones a una variable o método de clase.
     privado  Permite el acceso desde métodos de esta clase, así como desde métodos de clases heredadas públicamente . Otros tipos de acceso no son posibles;
     protegido  Permite el acceso a variables y métodos de clase solo desde métodos de la misma clase.
     virtual  Se aplica solo a los métodos de clase (no a los métodos de estructura) y le dice al compilador que este método debe colocarse en la tabla de funciones virtuales de la clase. 

    En este artículo, solo los modificadores públicos y privados serán relevantes para nosotros. El resto se abarcará en partes posteriores de la serie.

    En esencia, "público" significa que las variables y funciones se pueden usar/modificar en cualquier lugar, incluidos varios archivos .mq5 o .mqh. El modificador privado permite el acceso solo a la función definida en la clase actual.

    ¿Por qué son siquiera necesarios? Existen muchas razones, como la ocultación de datos, la abstracción, la mantenibilidad y la reutilización. 

    Nuestro código del constructor se verá así:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+

    El constructor/destructor no tiene tipo retornado.

    Aquí simplemente asignaremos un valor a las variables no inicializadas porque a veces podemos necesitar que una variable bool (var2 en este caso) se establezca en true como su valor inicial.

    Existe una forma alternativa de hacer esto:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name() : var1(0), var2(true)
      {
      }
    //+------------------------------------------------------------------+

    Este constructor no tiene cuerpo y simplemente inicializa los valores de las variables no inicializadas como queramos que sean. Podrá utilizar cualquiera de los métodos, aunque le recomiendo el segundo. Por ejemplo, si usa const al declarar una variable para hacerla inmutable, el primer método para asignar un valor a una variable no inicializada no funcionará, pero el segundo sí. Esto se debe a que en el primer método asignamos un valor y en el segundo lo inicializamos.

    O puede especificar en la lista de miembros lo siguiente:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name() : var1(0), var2(true) {}
                        ~name();
      };
    //+------------------------------------------------------------------+



    Ahora que hemos visto cómo declarar las variables, constructores y destructores, la creación de una función no será difícil.

    Si quiere crear una función llamada nombreFunción() que tome un parámetro como una variable de cadena y simplemente imprima la variable, esto se vería así: 

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class className
      {
    public:
       void              functionName(string printThis)
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void className::functionName(string printThis)
      {
       Print(printThis);
      }
    //+------------------------------------------------------------------+


    Declaramos una función en una lista de miembros de la clase llamada className con un modificador de acceso público para que sea posible usar esta función en cualquier lugar. Luego escribimos el cuerpo de la función.

    Tenga en cuenta que la declaración de una función en una clase, es decir

    functionName(string printThis)

    deberá coincidir exactamente al escribir el cuerpo de la función.

    Con esto concluye nuestra introducción básica a la escritura de clases en MQL5.


  4. ¿Cómo las usamos?


    Para entender mejor este punto, deberemos echar un vistazo a nuestra estructura de carpetas:

    • Test Project
      • mainFile.mq5
      • includeFile.mqh

    Observemos primero el código completo de nuestra clase, que escribimos en includeFile.mqh:

    En este ejemplo, declaramos una clase llamada className, que incluye un constructor, un destructor, tres variables (una privada y dos públicas) y una función pública.

    • Constructor: inicializamos las variables var0, var1 y var2 con los valores 10, 0 y true respectivamente.
    • Destructor: actualmente está vacío y por lo tanto no hace nada.
    • var0: variable entera privada inicializada en 10 y utilizada en la función (functionName).
    • var1: variable entera pública inicializada en 0 y también utilizada en la función (functionName).
    • functionName: la función vacía functionName toma el número entero printThisNumber e imprime la suma de printThisNumber, var0 y var1.

    A continuación, analizaremos mainFile.mq5:

    #include "includeFile.mqh"
    className classInstance;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       classInstance.functionName(5)//This line will print (5+10+0) = 15
       classInstance.var1 = 50;//This will change the value of var1 to 50
       classInstance.functionName(5)//Now, this line will print (5+10+50) = 65
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+

    Primero, incluiremos el archivo includeFile.mqh en mainFile.mq5. Luego crearemos una instancia de la clase usando

    className classInstance;

    Esta instancia nos permitirá modificar y utilizar variables o funciones de la clase. Este proceso se denomina creación de instancias, pero no profundizaremos aquí en su definición.
    Podemos crear tantas instancias como queramos y todas serán independientes entre sí.

    Hemos usado "" en lugar de <> para encontrar el archivo .mqh porque <> busca el archivo .mqh en la carpeta de inclusión, mientras que "" busca el archivo .mqh en el directorio actual.


    Creamos un panel usando un archivo .mqh

    Ahora crearemos un panel desde cero usando un archivo .mqh. De ser necesario, tomaremos prestados fragmentos de nuestro código anterior. Para organizar nuestros archivos de forma efectiva, crearemos una nueva carpeta Movable Dashboard MQL5.

    A continuación, crearemos dos archivos nuevos: Movable_Dashboard_MQL5.mq5 servirá como nuestro archivo .mq5 principal, mientras que el archivo GUI_Movable.mqh almacenará el código para desplazar el panel. Nombrar estos archivos correctamente resulta esencial para administrar varios archivos fácilmente.

    Primero, crearemos un panel en blanco de 200x200 usando el método Object Create en nuestro archivo .mq5 principal (Movable_Dashboard_MQL5.mq5) en OnInit():

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //---
       //Set the name of the rectangle as "TestRectangle"
       string name = "TestRectangle";
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
       ChartRedraw();
    //---
       return(INIT_SUCCEEDED);
      }

    Resultado: 

    Fig. 1. Panel base

    Fig. 1. Panel básico


    ¿Por qué creamos el panel en el archivo principal .mq5 (Movable_Dashboard_MQL5.mq5) y no en .mqh (GUI_Movable.mqh)? Esta decisión se toma principalmente por simplicidad y dependerá de nuestros objetivos específicos. Usaremos este enfoque en el siguiente apartado.

    Prestemos atención al archivo .mqh (GUI_Movable.mqh), que actualmente tiene el aspecto siguiente:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
       
      };
    //+------------------------------------------------------------------+

    Aquí simplemente declararemos una clase sin definir explícitamente el constructor y el destructor.

    Entonces, ¿cuál sería nuestro objetivo? Nuestro objetivo será adaptar este código para que pueda implementarse en nuestro archivo principal, haciendo así que nuestro panel sea móvil.

    ¿Cómo podemos lograr esto? A continuación le mostramos el código que debería hacer que el panel sea móvil en nuestro archivo .mq5 anterior (Movable_Dashboard_MQL5.mq5):

    //Declare some global variable that will be used in the OnChartEvent() function
    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
    //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
          
          string name = "TestRectangle";
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    

    Ahora procederemos de la siguiente forma: 

    1. Escribiremos el código para la clase GUI_Movable.
    2. Luego crearemos una instancia de la clase en el archivo principal .mq5,
    3. y le daremos un nombre a esta instancia.
    4. Después usaremos los métodos de la clase GUI_Movable para hacer que el panel sea móvil.

    Estos pasos pueden parecer complicados al principio, pero con la práctica el proceso resultará intuitivo.

    1. Vamos a escribir el código para la clase GUI_Movable:

      Necesitaremos planificar los componentes de nuestra clase. Aquí está el desglose:

      1. Tendremos que declarar seis variables con modificadores privados (previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance, movingState). Estas variables incluirán cinco números enteros y un valor booleano.
      2. Asimismo, deberemos declarar una séptima variable pública que almacenará el nombre del panel. También necesitaremos hacer pública esta variable, ya que deberemos cambiarla desde nuestro archivo principal .mq5.
      3. Necesitaremos encontrar una manera de usar la función OnChartEvent en el archivo .mqh ya que todas nuestras variables declaradas se encuentran allí y esas variables serán necesarias dentro de la función OnChartEvent.


      1. Comenzaremos declarando seis variables privadas en la clase, cinco de las cuales serán números enteros; la otra será booleana. Para inicializar estos valores, usaremos un constructor:

        //+------------------------------------------------------------------+
        //| Class GUI_Movable                                                |
        //+------------------------------------------------------------------+
        class GUI_Movable
          {
        private:
           int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
           bool              movingState;
        public:
                             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false) {};
          };
        //+------------------------------------------------------------------+

        Necesitaremos establecer 0 para todos los ints y false para bools como nuestros valores iniciales, así que usaremos un constructor para inicializarlos.

      2. A continuación, declararemos una variable pública para almacenar el nombre del panel. Deberíamos poder acceder a la variable desde nuestro archivo principal .mq5.

        public: 
           string Name;

        El valor inicial será, por supuesto, NULL, pero por pura formalidad lo inicializaremos como NULL y cambiaremos nuestro constructor a (esto es solo una formalidad, porque la cadena no causa inconsistencias)

        GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
        
      3. Este paso puede parecer un poco complejo, pero si lo entiende todo resultará más fácil.

        Luego crearemos una función pública llamada OnEvent que aceptará los siguientes datos de entrada: id, lparam, dparam y sparam. Como OnChartEvent() no retornará nada (void), también haremos que OnEvent() esté vacío.

        La función OnEvent hará todo lo que la función OnChartEvent() está diseñada para hacer, pero lo realizará en un archivo .mqh. Usaremos OnEvent() en la función OnChartEvent() real en el archivo principal.

        Para evitar los errores causados ​​por la declaración OnChartEvent() tanto en el archivo .mqh como en el archivo principal, crearemos una función aparte llamada OnEvent(). Luego la declararemos:

        public:
           string            Name;
           void              OnEvent(int id, long lparam, double dparam, string sparam);

        Ahora escribiremos el código de la función. Haremos todo aquello para lo que el OnChartEvent() original fue diseñado:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           
          }
        //+------------------------------------------------------------------+

        Hemos colocado esta función en un ámbito global. Ahora podremos poner el mismo código aquí y tendremos acceso a las variables declaradas en la clase.

        El código de función completo se verá así:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
           if(id == CHARTEVENT_MOUSE_MOVE)
             {
              //define X, Y, XDistance, YDistance, XSize, YSize
              int X = (int)lparam;
              int Y = (int)dparam;
              int MouseState = (int)sparam;
        
              string name = Name;
              int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
              int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
              int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
              int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
        
              if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
                {
                 mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
                 mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
                 mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
                 mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
        
                 if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                   {
                    movingState = true; //If yes the set movingState to True
                   }
        
                }
        
              if(movingState)//if movingState is true, Update the Dashboard position
                {
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
                 ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
                 ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
                 ChartRedraw(0); //Redraw Chart
                }
        
              if(MouseState == 0)//Check if MLB is not pressed
                {
                 movingState = false;//set movingState again to false
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
                }
        
              previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
             }
          }
        //+------------------------------------------------------------------+
        

        Lo único que cambiaremos es 

        string name = "TestRectangle";

        a

        string name = Name;

        ya que necesitaremos usar la variable Name que configuramos en el archivo principal .mq5.


    2. Luego crearemos una instancia de la clase en el archivo principal .mq5: 

      Esto se puede hacer de forma muy sencilla de la forma siguiente:

      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;

      Aquí hemos incluido el archivo .mqh seleccionado "" en lugar de <> para indicar la ubicación del archivo. <> busca el archivo .mqh en la carpeta de inclusión, mientras que "" busca el archivo .mqh en el directorio actual, que en este caso es la carpeta Movable Dashboard MQL5. Luego declararemos una instancia de la clase GUI_Movable y la llamaremos Dashboard. Este nombre nos permitirá utilizar el código que hemos escrito en el archivo .mqh.

    3. Ahora le daremos un nombre a esta instancia:

      Esto se puede hacer fácilmente usando la función OnInit(). Así es como debería verse nuestra función OnInit():

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }

      Finalmente, usaremos
      //Give dashboard's name to the class instance
      Dashboard.Name = name;

      para asignar la variable Name a la instancia Dashboard de la clase GUI_Movable. Esto se usará más adelante en la función OnEvent() dentro de la instancia. Resulta esencial establecer la propiedad CHART_EVENT_MOUSE_MOVE en true. Esto permitirá detectar los eventos del ratón. Repetiremos este paso en el constructor más tarde. Por ahora, no tiene sentido complicar la tarea.

    4. Usaremos los métodos de la clase GUI_Movable para hacer que el panel sea móvil: 

      A pesar de que el nombre resulta algo complicado, este paso es sencillo.

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }

      En este punto, colocaremos la función OnEvent() en OnChartEvent() para usar la funcionalidad OnChartEvent() en el archivo .mqh.

    Finalmente, aquí tenemos nuestro código completo:

    Estructura de las carpetas:

    • Movable Dashboard MQL5
      • Movable_Dashboard_MQL5.mq5
      • GUI_Movable.mqh

    1. Movable_Dashboard_MQL5.mq5
      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }
      //+------------------------------------------------------------------+
      
    2. GUI_Movable.mqh
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
        {
         //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
         if(id == CHARTEVENT_MOUSE_MOVE)
           {
            //define X, Y, XDistance, YDistance, XSize, YSize
            int X = (int)lparam;
            int Y = (int)dparam;
            int MouseState = (int)sparam;
      
            string name = Name;
            int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
            int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
            int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
            int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
      
            if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
              {
               mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
               mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
               mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
               mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
      
               if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                 {
                  movingState = true; //If yes the set movingState to True
                 }
      
              }
      
            if(movingState)//if movingState is true, Update the Dashboard position
              {
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
               ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
               ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
               ChartRedraw(0); //Redraw Chart
              }
      
            if(MouseState == 0)//Check if MLB is not pressed
              {
               movingState = false;//set movingState again to false
               ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
              }
      
            previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
           }
        }
      //+------------------------------------------------------------------+
      

    Primero compilaremos el archivo .mqh, y luego el archivo .mq5. Así, se creará un archivo .ex5 que se podrá ejecutar en el gráfico.

    Con estos pasos, replicaremos lo que logramos en la primera parte usando un código más eficiente. Tenga en cuenta la diferencia significativa en la cantidad de código utilizado en el archivo .mq5 principal en las partes 1 y 2. Y lo más agradable es que solo mejorará.


    Resultado:

    Fig. 2. Panel móvil sencillo

    Fig. 2. Panel móvil simple



    Instalamos dos paneles en un gráfico utilizando un archivo .mqh.

    Ahora, en lugar de crear el panel usando ObjectCreate en nuestro archivo .mq5 principal, lo haremos en nuestro archivo .mqh. Verá que así todo le resultará fácil más adelante.

    Ahora vamos a profundizar en los cambios que introduciremos en nuestro archivo .mqh:

    1. Así, necesitaremos cambiar el modificador de la variable de cadena Name de público a privado. El nombre no será necesario en nuestro archivo principal; la variable solo se necesitará en el archivo .mqh. Vamos a hacerla privada. Esto se puede hacer así:

      De:
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      	             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };

      A: 

      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
         string            Name;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };

      Simplemente hemos cambiado la ubicación.

      string            Name;
      Esto ha cambiado el modificador de la variable de público a privado.

    2. A continuación, añadiremos un método público llamado CreateDashboard(). El método aceptará los siguientes parámetros de entrada: name (string), xDis (int), yDis (int), xSize (int), ySize (int).

      Primero agregaremos todo esto a la lista de miembros de la clase:

      public:
         void              OnEvent(int id, long lparam, double dparam, string sparam);
         void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);

      Ahora definiremos esta función en el espacio global copiando el código de nuestro archivo principal de esta manera:

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Name = name;
         //Redraw Chart
         ChartRedraw();
      }
      //+------------------------------------------------------------------+
      


    Después de esto deberemos modificar nuestro archivo .mq5:

    #include "GUI_Movable.mqh"
    GUI_Movable Dashboard1;
    GUI_Movable Dashboard2;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       Dashboard1.CreateDashboard("Dashboard1", 100, 100, 200, 200);
       Dashboard2.CreateDashboard("Dashboard2", 100, 350, 200, 200);
    //---
       return(INIT_SUCCEEDED);
      }
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       Dashboard1.OnEvent(id, lparam, dparam, sparam);
       Dashboard2.OnEvent(id, lparam, dparam, sparam);
      }
    //+------------------------------------------------------------------+
    

    Desglosemos este código:

    Comenzaremos incluyendo el archivo GUI_Movable.mqh, que contiene la definición de la clase GUI_Movable. Esta clase tiene los métodos para crear y manejar los eventos vinculados con el panel móvil.

    A continuación, declararemos dos instancias de la clase GUI_Movable: Dashboard1 y Dashboard2. Estas instancias representarán los dos paneles que crearemos y monitorearemos en nuestro programa.

    En la función OnInit(), que se llama automáticamente al iniciarse el asesor, crearemos dos paneles llamando al método CreateDashboard() en nuestras dos instancias. Después transmitiremos el nombre del panel de información, su posición y su tamaño (en píxeles) como parámetros a este método. Luego, la función retornará INIT_SUCCEEDED, lo cual indica que la inicialización ha tenido éxito.

    Finalmente, tenemos la función OnChartEvent(), que se activará cada vez que ocurra un evento (como un clic o un movimiento del ratón) en el gráfico. En esta función, llamaremos al método OnEvent() en las dos instancias de nuestro panel, transmitiendo todos los parámetros recibidos. Esto permitirá que cada panel procese el evento de forma independiente según la lógica definida en el método OnEvent() de la clase GUI_Movable.

    Como podemos ver, este enfoque resulta simple, directo y aún conserva la misma funcionalidad. Esto hará que el código sea muy cómodo de usar en asesores/indicadores completos.

    Código completo del archivo .mqh:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
    private:
       int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
       bool              movingState;
       string            Name;
    public:
    		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
       void              OnEvent(int id, long lparam, double dparam, string sparam);
       void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = Name;
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
       //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
       ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
       //Give dashboard's name to the class instance
       Name = name;
       //Redraw Chart
       ChartRedraw();
    }
    //+------------------------------------------------------------------+
    

    Resultado: 

    Fig. 3. Dos paneles móviles en un gráfico

    Fig. 3. Dos paneles móviles en un gráfico


    Si hablamos de la jerarquía de los paneles, el creado anteriormente estará en una posición más baja en comparación con el panel creado posteriormente.


    Conclusión

    Para aquellos que quieran hacer móvil un panel propio ya existente, el proceso será bastante sencillo. Después de estudiar la sección "Creamos un panel usando un archivo .mqh", se convencerá de que podemos hacer que cualquier panel se mueva con unas pocas líneas de código en un asesor/indicador existente. Todo lo que necesita será incluir el archivo GUI_Movable.mqh y crear una instancia de la clase con el nombre del panel asignado a esta instancia. Con estos sencillos pasos, su panel será interactivo y fácil de desplazar con el ratón.


    Hoy hemos aprendido a aumentar la interactividad de nuestro panel, haciéndolo móvil. Los conocimientos adquiridos se pueden aplicar a cualquier asesor/indicador existente o a la creación de uno nuevo desde cero.

    El artículo ha resultado bastante extenso. El concepto de clases es bastante difícil de explicar y comprender, pero creo que este conocimiento le beneficiará enormemente a la hora de dominar la programación.

    Espero sinceramente que este artículo le haya resultado útil de una forma u otra.

    ¡Le deseo mucha suerte en la programación y el trading!


    Traducción del inglés realizada por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/en/articles/12880

    Archivos adjuntos |
    MQL5.zip (5.01 KB)
    Redes neuronales: así de sencillo (Parte 48): Métodos para reducir la sobreestimación de los valores de la función Q Redes neuronales: así de sencillo (Parte 48): Métodos para reducir la sobreestimación de los valores de la función Q
    En el artículo anterior, presentamos el método DDPG, que nos permite entrenar modelos en un espacio de acción continuo. Sin embargo, al igual que otros métodos de aprendizaje Q, el DDPG tiende a sobreestimar los valores de la función Q. Con frecuencia, este problema provoca que entrenemos los agentes con una estrategia subóptima. En el presente artículo, analizaremos algunos enfoques para superar el problema mencionado.
    Desarrollo de un sistema de repetición — Simulación de mercado (Parte 23): FOREX (IV) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 23): FOREX (IV)
    La creación ahora se realiza en el mismo punto en el que convertimos los ticks en barras. Así, si algo va mal durante la conversión, nos daremos cuenta del error enseguida. Esto se debe a que el mismo código que coloca las barras de 1 minuto en el gráfico cuando avanzamos rápidamente también se utiliza para el sistema de posicionamiento y para colocar las barras durante el avance normal. En otras palabras, el código responsable de esta tarea ya no se duplica en ningún lugar. De esta manera, tenemos un sistema mucho más adecuado tanto para el mantenimiento como para las mejoras.
    ¿Puede Heiken Ashi dar buenas señales en combinación con las medias móviles? ¿Puede Heiken Ashi dar buenas señales en combinación con las medias móviles?
    Las combinaciones de estrategias pueden mejorar el rendimiento de las transacciones. Podemos combinar indicadores y patrones para obtener confirmaciones adicionales. Las medias móviles nos ayudan a confirmar tendencias y seguirlas. Se trata del indicador técnico más famoso, lo cual se explica por su sencillez y su probada eficacia de análisis.
    Desarrollo de un sistema de repetición — Simulación de mercado (Parte 22): FOREX (III) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 22): FOREX (III)
    Para aquellos que aún no han comprendido la diferencia entre el mercado de acciones y el mercado de divisas (forex), a pesar de que este ya es el tercer artículo en el que abordo esto, debo dejar claro que la gran diferencia es el hecho de que en forex no existe, o mejor dicho, no se nos informa acerca de algunas cosas que realmente ocurrieron en la negociación.