Português
preview
Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con integración RestAPI  (Parte 4): Organización de funciones en clases en MQL5

Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con integración RestAPI (Parte 4): Organización de funciones en clases en MQL5

MetaTrader 5Ejemplos | 9 abril 2024, 09:51
192 0
Jonathan Pereira
Jonathan Pereira

Introducción

Les damos la bienvenida a la cuarta parte de nuestra jornada, en la que exploramos la creación de un agente de Aprendizaje por Refuerzo en MQL5 con integración RestAPI. Hasta ahora, hemos abordado aspectos importantes, como usar RestAPIs en MQL5, crear funciones MQL5 para interactuar con la API REST de tres en raya y realizar jugadas automáticas y scripts de prueba. Esto nos ha proporcionado una base sólida y nos ha ayudado a entender cómo MQL5 se conecta con elementos externos.

En este artículo, vamos a dar un paso importante y organizar nuestras funciones en clases dentro de MQL5. Vamos a utilizar la programación orientada a objetos, que es una forma de escribir código que ayuda a mantener las cosas organizadas y fáciles de entender. Es importante porque facilita la reparación y mejora del código. Un código bien organizado y modular también puede utilizarse en diferentes partes del proyecto o incluso en proyectos futuros.

En el resto del artículo, vamos a explicar cómo reestructurar nuestras funciones MQL5 existentes en clases. Mostraremos cómo esto puede hacer que el código sea más legible y eficiente. Vamos a dar ejemplos prácticos de cómo hacerlo y mostrar cómo esto puede facilitar el mantenimiento y la mejora del código.

La programación orientada a objetos (POO) es una forma poderosa de desarrollar software. En MQL5, utilizar clases es un gran avance respecto al método procedimental de escribir código. En esta parte, vamos a explorar cómo esto puede mejorar la calidad de nuestro proyecto. Vamos a considerar cuatro aspectos importantes:

  1. Encapsulamiento y modularidad: Las clases ayudan a organizar funciones relacionadas y variables en un solo lugar, lo que facilita el mantenimiento y reduce los errores.

  2. Reutilización de código: Una vez que escribes una clase, puedes usarla en muchos lugares diferentes, lo que ahorra tiempo y mantiene la consistencia del código.

  3. Facilidad de mantenimiento y mejora: Con funciones organizadas en clases, es más fácil encontrar y corregir errores o hacer mejoras. La estructura clara hace que el código sea más accesible.

  4. Abstracción y flexibilidad: Las clases facilitan la abstracción al ocultar la complejidad y exponer solo lo necesario. Esto hace que el código sea más intuitivo y flexible.

Vamos a demostrar que reorganizar nuestras funciones en clases en MQL5 no solo es para hacer que el código se vea mejor. Es un cambio significativo que hace que el código sea más eficiente, fácil de entender y de mantener. Explicaremos la transformación de funciones aisladas en métodos de clases bien definidos, lo que brinda beneficios inmediatos y a largo plazo. Esto no solo mejora nuestro proyecto actual, sino que también nos ayuda a crear una base sólida para futuros proyectos en MQL5.


Estado actual del código 

En el estado actual, nuestro código está compuesto por una serie de funciones destinadas a gestionar solicitudes HTTP, como SendGetRequest, SendPostRequest y Request. Estas funciones son responsables de enviar solicitudes GET y POST a una API, procesar las respuestas y manejar posibles errores.

//+------------------------------------------------------------------+
//|                                                      Request.mqh |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.00"

#define ERR_HTTP_ERROR_FIRST        ERR_USER_ERROR_FIRST+1000 //+511

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   int data_size = StringLen(query_param);

   if(data_size > 0)
     {
      StringToCharArray(query_param, data, 0, data_size);
      res = WebRequest("GET", url + "?" + query_param, NULL, NULL, timeout, data, data_size, result, result_headers);
     }
   else
     {
      res = WebRequest("GET", url, headers, timeout, data, result, result_headers);
     }

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return (0);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int SendPostRequest(const string url, const string payload, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);

   if(headers == "")
     {
      headers = "Content-Type: application/json\r\n";
     }

   res = WebRequest("POST", url, headers, timeout, data, result, result_headers);

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return res;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int Request(string method,
            string &out,
            const string url,
            const string payload = "",
            const string query_param = "",
            string headers = "",
            const int timeout = 5000)
  {
   ResetLastError();

   if(method == "GET")
     {
      return SendGetRequest(url, query_param, out, headers, timeout);
     }
   else
      if(method == "POST")
        {
         return SendPostRequest(url, payload, out, headers, timeout);
        }

   return -1;
  }
//+------------------------------------------------------------------+


Desafíos y problemas de este enfoque:

  1. Falta de encapsulación y modularidad: Las funciones están actualmente dispuestas de manera aislada, sin un mecanismo claro que las agrupe por funcionalidad o propósito. Esto produce un código con baja cohesión y alto acoplamiento, lo que complica el mantenimiento y la comprensión del flujo lógico.

  2. Reutilización limitada del código: Como las funciones son específicas y no están organizadas en una estructura modular, la reutilización del código en diferentes contextos o proyectos es limitada. Esto puede provocar la duplicación de código, aumentando el riesgo de inconsistencias y errores.

  3. Mantenimiento y extensibilidad complicados: Sin una clara separación de responsabilidades, la identificación y corrección de errores, así como la adición de nuevas funcionalidades, se convierten en tareas complejas. Esto es especialmente problemático en proyectos en expansión o que requieren actualización constante.

Ejemplos de organización actual de las funciones:

Las funciones, tal como están actualmente, siguen un patrón procedimental. Por ejemplo, la función SendGetRequest acepta parámetros para una URL, parámetros de consulta y otros, y devuelve un resultado basado en la respuesta de WebRequest. De manera similar, SendPostRequest maneja solicitudes POST. La función Request actúa como un facilitador para llamar a las funciones GET y POST, dependiendo del método HTTP especificado.

Función SendGetRequest:

int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   int data_size = StringLen(query_param);

   if(data_size > 0)
     {
      StringToCharArray(query_param, data, 0, data_size);
      res = WebRequest("GET", url + "?" + query_param, NULL, NULL, timeout, data, data_size, result, result_headers);
     }
   else
     {
      res = WebRequest("GET", url, headers, timeout, data, result, result_headers);
     }

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return (0);
  }

Función SendPostRequest:

int SendPostRequest(const string url, const string payload, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);

   if(headers == "")
     {
      headers = "Content-Type: application/json\r\n";
     }

   res = WebRequest("POST", url, headers, timeout, data, result, result_headers);

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return res;
  }

Observe que las funciones contienen varios elementos que se repiten, lo que hace que el mantenimiento de respuestas a errores, por ejemplo, sea un desafío, pues puede ser que se aplique en una parte y se negligencie en otra, o situaciones similares.

Este enfoque, aunque funcional, no aprovecha los beneficios de la orientación a objetos, como la encapsulación y la modularidad. Cada función opera de manera relativamente independiente, sin una estructura unificada que las conecte o gestione su comportamiento de forma cohesiva.


La importancia de la programación orientada a objetos (POO)

La programación orientada a objetos (POO) es un paradigma de programación que utiliza "objetos" como elementos fundamentales. Estos objetos son estructuras de datos compuestas por campos de datos y procedimientos, denominados métodos, que representan entidades o conceptos del mundo real. En la POO, cada objeto tiene la capacidad de recibir y enviar mensajes, además de procesar datos, al mismo tiempo que actúa como una unidad autónoma con funciones o responsabilidades específicas dentro del sistema de software.

Beneficios de la POO en el mantenimiento y la escalabilidad de proyectos:

  1. Mantenimiento simplificado: La POO facilita el mantenimiento de software debido a su diseño modular. Cada objeto es una entidad independiente con su propia lógica y datos, lo que significa que cambios en un objeto específico generalmente no afectan a otros. Esto hace que el proceso de actualización, corrección de errores y mejora del sistema sea mucho más manejable.

  2. Escalabilidad mejorada: La POO permite que los desarrolladores construyan sistemas que pueden escalarse fácilmente en tamaño y complejidad. La adición de nuevas funcionalidades se vuelve más eficiente, pues los nuevos objetos pueden crearse con características específicas, sin necesidad de modificar mucho el código existente.

  3. Reutilización de código: La herencia, uno de los pilares de la POO, permite que los desarrolladores creen nuevas clases basadas en clases existentes. Esto promueve la reutilización de código, reduce la redundancia y facilita el mantenimiento.

¿Cómo la modularidad ayuda a mejorar el código?

La modularidad es uno de los principales beneficios de la POO. Permite a los desarrolladores:

  1. Dividir sistemas complejos: Con la POO, un sistema complejo puede dividirse en componentes más pequeños y manejables (objetos), cada uno con responsabilidades bien definidas. Esto hace que el sistema sea más fácil de entender, desarrollar y mantener.

  2. Foco en la abstracción: La modularidad permite a los desarrolladores centrarse en la abstracción, para trabajar con conceptos de alto nivel en vez de detalles de bajo nivel. Esto facilita la resolución de problemas complejos y mejora la claridad del código.

  3. Promover la flexibilidad y la extensibilidad: Objetos y clases pueden diseñarse para ser flexibles y extensibles, para permitir que un sistema evolucione y se adapte con el tiempo sin la necesidad de una reescritura completa.

  4. Fomentar la colaboración: En un entorno de desarrollo colaborativo, diferentes equipos o desarrolladores pueden trabajar en distintos módulos u objetos simultáneamente, lo que incrementa la eficiencia y reduce el tiempo de desarrollo.

La adopción de la POO en nuestro proyecto con integración RestAPI proporciona un enfoque robusto para manejar la complejidad del software, lo cual mejora significativamente la mantenibilidad, la escalabilidad y la calidad general del código.


Refactorización de funciones en clases

Ahora que entendemos la importancia de la programación orientada a objetos (POO) y cómo puede mejorar la mantenibilidad y escalabilidad de nuestros proyectos, vamos a refactorizar las funciones existentes en clases. Para ilustrar mejor este proceso, incluiremos un diagrama que mostrará cómo el nuevo código orientado a objetos estará más organizado y claro. Vamos a seguir un proceso paso a paso para transformar nuestro código procedimental en un código orientado a objetos más organizado y claro.


Implementación:

Paso 1: Definir Interfaces, comenzamos definiendo interfaces para nuestros objetos que describirán los métodos y funcionalidades que deben tener. En nuestro caso, tenemos dos interfaces: IHttpRequest e IHttpResponseProcessor. Estas interfaces establecen los contratos que nuestras clases concretas deben seguir.

//+------------------------------------------------------------------+
//| Interface para HttpRequest                                      |
//+------------------------------------------------------------------+
interface IHttpRequest
{
public:
   virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = "") = 0;
   virtual int ValidateMethod(string method) = 0;
   virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) = 0;
   virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) = 0;
};

//+------------------------------------------------------------------+
//| Interface para HttpResponseProcessor                            |
//+------------------------------------------------------------------+
interface IHttpResponseProcessor
{
public:
   virtual int ProcessResponse(int res, string &out, uchar &result[]) = 0;
   virtual int ProcessSuccessResponse(string &out, uchar &result[]) = 0;
   virtual int ProcessErrorResponse(int res, string &out, uchar &result[]) = 0;
   virtual int DetectAndSkipBOM(uchar &result[], int size) = 0;
};

Paso 2: Crear Clases Abstractas, creamos clases abstractas que implementan estas interfaces. Estas clases abstractas no tienen una implementación real de los métodos, pero definen su estructura. Las clases abstractas son HttpResponseProcessorBase y HttpRequestBase.

//+------------------------------------------------------------------+
//| Classe base abstrata para HttpResponseProcessor                  |
//+------------------------------------------------------------------+
class HttpResponseProcessorBase : public IHttpResponseProcessor
{
public:
   HttpResponseProcessorBase() {}
   virtual int ProcessResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int ProcessSuccessResponse(string &out, uchar &result[]) override = 0;
   virtual int ProcessErrorResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int DetectAndSkipBOM(uchar &result[], int size) override = 0;
};

//+------------------------------------------------------------------+
//| Classe base abstrata para HttpRequest                            |
//+------------------------------------------------------------------+
class HttpRequestBase : public IHttpRequest
{
protected:
   string m_headers;
   int m_timeout;
   IHttpResponseProcessor *responseProcessor;

public:
   HttpRequestBase(string headers = "", int timeout = 5000) : m_headers(headers), m_timeout(timeout)
   {
      if (responseProcessor == NULL)
      {
         responseProcessor = new HttpResponseProcessor();
      }
   }
   virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = "") override;
   virtual int ValidateMethod(string method) override;
   virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override = 0;
   virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override = 0;
   virtual int ProcessResponse(int res, string &out, uchar &result[]) = 0;
};

Clase HttpRequestBase:

  1. HttpRequestBase(string headers = "", int timeout = 5000): Este es el constructor de la clase HttpRequestBase. Acepta dos parámetros opcionales, headers y timeout, que especifican los encabezados HTTP a enviar en las solicitudes y el tiempo límite para la respuesta, respectivamente. El constructor inicializa estos valores y crea una instancia de la clase HttpResponseProcessor (la clase que procesa las respuestas HTTP).

  2. virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = ""): Este es un método virtual que permite realizar una solicitud HTTP. Acepta el método HTTP (GET o POST), la URL de destino, un posible cuerpo de la solicitud (payload), y parámetros de consulta (query_param). Coordina la llamada a las funciones PerformGetRequest o PerformPostRequest basado en el método especificado y luego procesa la respuesta mediante el método ProcessResponse.

  3. virtual int ValidateMethod(string method): Este método verifica si el método HTTP especificado es válido (GET o POST). Retorna verdadero si es válido y falso en caso contrario.

  4. virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param): Este es un método virtual abstracto que debe ser implementado por las clases derivadas. Realiza una solicitud HTTP GET a la URL especificada y devuelve los datos de la respuesta en el parámetro data, el resultado en el parámetro result, y los encabezados de la respuesta en result_headers.

  5. virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload): Este es un método virtual abstracto que debe ser implementado por las clases derivadas. Realiza una solicitud HTTP POST a la URL especificada con el cuerpo de la solicitud (payload) y devuelve los datos de la respuesta en el parámetro data, el resultado en el parámetro result, y los encabezados de la respuesta en result_headers.

  6. virtual int ProcessResponse(int res, string &out, uchar &result[]): Este es un método virtual abstracto que debe ser implementado por las clases derivadas. Procesa la respuesta HTTP basado en el código de respuesta res. Si la respuesta es exitosa (código de respuesta entre 200 y 299), llama a ProcessSuccessResponse. De lo contrario, llama a ProcessErrorResponse. El resultado se almacena en out, y los datos brutos de la respuesta están en result.


Paso 3: Crear Clases Concretas, creamos clases concretas que implementan los métodos de las interfaces. Las clases concretas son HttpRequest y HttpResponseProcessor.

//+------------------------------------------------------------------+
//| Classe concreta para HttpRequest                                 |
//+------------------------------------------------------------------+
class HttpRequest : public HttpRequestBase
{
public:
   HttpRequest(string headers = "", int timeout = 5000) : HttpRequestBase(headers, timeout) {}

   virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override;
   virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override;
   virtual int ProcessResponse(int res, string &out, uchar &result[]) override;
};

//+------------------------------------------------------------------+
//| Classe concreta para HttpResponseProcessor                       |
//+------------------------------------------------------------------+
class HttpResponseProcessor : public HttpResponseProcessorBase
{
public:
   virtual int ProcessResponse(int res, string &out, uchar &result[]) override;
   virtual int ProcessSuccessResponse(string &out, uchar &result[]) override;
   virtual int ProcessErrorResponse(int res, string &out, uchar &result[]) override;
   virtual int DetectAndSkipBOM(uchar &result[], int size) override;
};


Paso 4: Implementar Métodos de las Clases Concretas, implementamos los métodos de las clases concretas con las funcionalidades reales. Aquí, tenemos los métodos PerformGetRequest, PerformPostRequest, ProcessResponse, ProcessSuccessResponse, ProcessErrorResponse y DetectAndSkipBOM.

int HttpRequest::PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param)
{
   if (StringLen(query_param) > 0)
      return WebRequest("GET", url + "?" + query_param, NULL, NULL, m_timeout, data, StringLen(query_param), result, result_headers);

   return WebRequest("GET", url, m_headers, m_timeout, data, result, result_headers);
}

int HttpRequest::PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload)
{
   if (m_headers == "")
      m_headers = "Content-Type: application/json\r\n";
   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);
   return WebRequest("POST", url, m_headers, m_timeout, data, result, result_headers);
}

int HttpRequest::ProcessResponse(int res, string &out, uchar &result[])
{
   if (res >= 200 && res <= 299)
      return responseProcessor.ProcessSuccessResponse(out, result);

   return responseProcessor.ProcessErrorResponse(res, out, result);
}

int HttpResponseProcessor::ProcessResponse(int res, string &out, uchar &result[])
{
   if (res >= 200 && res <= 299)
      return ProcessSuccessResponse(out, result);

   return ProcessErrorResponse(res, out, result);
}

int HttpResponseProcessor::ProcessSuccessResponse(string &out, uchar &result[])
{
   int size = ArraySize(result);
   int start_index = DetectAndSkipBOM(result, size);
   out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
   return 0;
}

int HttpResponseProcessor::ProcessErrorResponse(int res, string &out, uchar &result[])
{
   ResetLastError();
   if (res == -1)
      return GetLastError();
   else if (res >= 100 && res <= 511)  // Erros HTTP
   {
      out = CharArrayToString(result);
      Print(out);
      return res;
   }
   return res;
}

int HttpResponseProcessor::DetectAndSkipBOM(uchar &result[], int size)
{
   int start_index = 0;
   for (int i = 0; i < MathMin(size, 3); i++)
   {
      if (result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
         start_index = i + 1;
      else
         break;
   }
   return start_index;
}

Clase HttpRequest:

  1. HttpRequest(string headers = "", int timeout = 5000): Este es el constructor de la clase HttpRequest. Llama al constructor de la clase base HttpRequestBase para inicializar las configuraciones de encabezado y tiempo límite.

  2. virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param): Esta es la implementación del método PerformGetRequest en la clase HttpRequest. Realiza una solicitud HTTP GET a la URL especificada, incluyendo los parámetros de consulta, si los hay. Los datos brutos de la respuesta se almacenan en data, el resultado en result, y los encabezados de la respuesta en result_headers.

  3. virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload): Esta es la implementación del método PerformPostRequest en la clase HttpRequest. Realiza una solicitud HTTP POST a la URL especificada, incluyendo el cuerpo de la solicitud (payload). Los datos brutos de la respuesta se almacenan en data, el resultado en result, y los encabezados de la respuesta en result_headers.

  4. virtual int ProcessResponse(int res, string &out, uchar &result[]): Esta es la implementación del método ProcessResponse en la clase HttpRequest. Llama a ProcessSuccessResponse si la respuesta es exitosa (código de respuesta entre 200 y 299) y a ProcessErrorResponse en caso contrario. El resultado se almacena en out, y los datos brutos de la respuesta están en result.


Beneficios de la refactorización:

La refactorización del código de nuestro proyecto, donde pasamos de un enfoque procedimental a uno orientado a objetos, trae varios beneficios significativos. Discutiremos esos beneficios comparando el código anterior con el nuevo código que utiliza clases, y nos enfocaremos en cómo esta mejora la legibilidad, la mantenibilidad y la adaptabilidad del código.

Comparación entre el código anterior y el nuevo código con clases:

Código anterior (procedimental):

  • Estructura: El código consistía en funciones independientes (SendGetRequest, SendPostRequest, Request) que manejaban diferentes aspectos de las solicitudes HTTP.
  • Mantenibilidad: Cambios en una función podrían requerir cambios similares en otras, ya que el código era repetitivo y no compartía lógica común de manera eficiente.
  • Legibilidad: Aunque cada función era relativamente simple, entender el código en su conjunto era más desafiante, especialmente para nuevos desarrolladores.

Nuevo código (orientado a objetos):

  • Estructura: Introducción de interfaces (IHttpRequest, IHttpResponseProcessor) y clases abstractas (HttpRequestBase, HttpResponseProcessorBase), seguidas por implementaciones concretas (HttpRequest, HttpResponseProcessor).
  • Mantenibilidad: El código ahora es más modular, con responsabilidades claramente definidas para cada clase. Esto facilita la actualización y el ajuste del código, pues los cambios en una clase generalmente no afectan a otras.
  • Legibilidad: La organización en clases y métodos hace que el código sea más intuitivo. Cada clase y método tiene un propósito claro, lo que facilita comprender qué hace el código y cómo funciona.

Mejora en la legibilidad y mantenibilidad:

Legibilidad:

  • Organización lógica: El código ahora está dividido en clases con funciones específicas, lo que hace más fácil entender la relación entre diferentes partes del código.
  • Nombres descriptivos: Con la utilización de clases y métodos, los nombres pueden ser más descriptivos, para reflejar claramente la funcionalidad de cada parte del código.

Mantenibilidad:

  • Facilidad de actualización: Los cambios en una parte del código (como la lógica de procesamiento de respuesta HTTP) pueden hacerse en un solo lugar, sin necesidad de alterar múltiples funciones dispersas por el código.
  • Extensibilidad: Agregar nuevas funcionalidades o adaptar el código a nuevos requisitos es más sencillo, pues la estructura orientada a objetos está diseñada para expansión y flexibilidad.
Adaptabilidad a cambios futuros:
  • Escalabilidad: A medida que el proyecto crece, agregar nuevas funcionalidades o integrar con otras APIs y sistemas es más fácil. Las clases pueden extenderse o nuevas clases pueden crearse basadas en las existentes.
  • Reuso de código: Los componentes pueden reutilizarse en diferentes partes del proyecto o incluso en otros proyectos, lo que ahorra tiempo y esfuerzo.
  • Facilidad de pruebas: Probar el código se hace más fácil, ya que es posible enfocarse en unidades específicas (clases o métodos) de forma aislada.

La refactorización de nuestro código a un enfoque orientado a objetos fue un cambio estratégico que no solo mejoró la calidad actual de nuestro proyecto, sino también estableció una fundación sólida para su desarrollo futuro. Con esta transformación, logramos un código más limpio, más fácil de entender, mantener y expandir.

Al encapsular la lógica en clases bien definidas, redujimos la redundancia, mejoramos la claridad y aumentamos la eficiencia de nuestro código. Esto es especialmente importante en un ambiente en constante cambio, donde la adaptabilidad y la capacidad de responder rápidamente a nuevos requisitos es importante.

Además, la modularidad obtenida con la programación orientada a objetos facilita la colaboración en equipo, donde diferentes partes del proyecto pueden ser trabajadas simultáneamente con menos riesgo de conflictos de código. También abre puertas para prácticas de desarrollo más avanzadas, como las pruebas unitarias, que son más fáciles de implementar en una estructura orientada a objetos.

//+------------------------------------------------------------------+
//|                                                     Requests.mqh |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.05"

//+------------------------------------------------------------------+
//| Interface para HttpRequest                                      |
//+------------------------------------------------------------------+
interface IHttpRequest
  {
public:
   virtual int       Request(string method, string &out, const string url, const string payload = "", const string query_param = "") = 0;
   virtual int       ValidateMethod(string method) = 0;
   virtual int       PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) = 0;
   virtual int       PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) = 0;
  };



//+------------------------------------------------------------------+
//| Interface para HttpResponseProcessor                            |
//+------------------------------------------------------------------+
interface IHttpResponseProcessor
  {
public:
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) = 0;
   virtual int       ProcessSuccessResponse(string &out, uchar &result[]) = 0;
   virtual int       ProcessErrorResponse(int res, string &out, uchar &result[]) = 0;
   virtual int       DetectAndSkipBOM(uchar &result[], int size) = 0;
  };



//+------------------------------------------------------------------+
//| Classe base abstrata para HttpResponseProcessor                  |
//+------------------------------------------------------------------+
class HttpResponseProcessorBase : public IHttpResponseProcessor
  {
public:
                     HttpResponseProcessorBase() {};
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int       ProcessSuccessResponse(string &out, uchar &result[]) override = 0;
   virtual int       ProcessErrorResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int       DetectAndSkipBOM(uchar &result[], int size) override = 0;
  };



//+------------------------------------------------------------------+
//| Classe base abstrata para HttpRequest                            |
//+------------------------------------------------------------------+
class HttpRequestBase : public IHttpRequest
  {
protected:
   string            m_headers;
   int               m_timeout;
   IHttpResponseProcessor *responseProcessor;

public:
                     HttpRequestBase(string headers = "", int timeout = 5000) : m_headers(headers), m_timeout(timeout)
     {

      if(responseProcessor == NULL)
        {
         responseProcessor = new HttpResponseProcessor();
        }

     }
   virtual int       Request(string method, string &out, const string url, const string payload = "", const string query_param = "") override;
   virtual int       ValidateMethod(string method) override;
   virtual int       PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override = 0;
   virtual int       PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override = 0;
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) = 0;
  };
//+------------------------------------------------------------------+
//| Implementação da função Request na classe HttpRequestBase       |
//+------------------------------------------------------------------+
int HttpRequestBase::Request(string method, string &out, const string url, const string payload, const string query_param) override
  {
   if(!ValidateMethod(method))
     {
      out = "Método HTTP inválido.";
      return -1;
     }

   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   if(method == "GET")
      res = PerformGetRequest(data, result, result_headers, url, query_param);
   else
      if(method == "POST")
         res = PerformPostRequest(data, result, result_headers, url, payload);

   if(res >= 0)
      return ProcessResponse(res, out, result);
   else
     {
      out = "Erro ao realizar a solicitação HTTP.";
      return res;
     }
  }
//+------------------------------------------------------------------+
//| Implementação da função ValidateMethod na classe HttpRequestBase |
//+------------------------------------------------------------------+
int HttpRequestBase::ValidateMethod(string method)
  {
   return (method == "GET" || method == "POST");
  }



//+------------------------------------------------------------------+
//| Classe concreta para HttpRequest                                 |
//+------------------------------------------------------------------+
class HttpRequest : public HttpRequestBase
  {
public:
                     HttpRequest(string headers = "", int timeout = 5000) : HttpRequestBase(headers, timeout) {}

   virtual int       PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override;
   virtual int       PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override;
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) override;
  };
//+------------------------------------------------------------------+
//| Implementação das funções da classe HttpRequest                  |
//+------------------------------------------------------------------+
int HttpRequest::PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param)
  {
   if(StringLen(query_param) > 0)
      return WebRequest("GET", url + "?" + query_param, NULL, NULL, m_timeout, data, StringLen(query_param), result, result_headers);

   return WebRequest("GET", url, m_headers, m_timeout, data, result, result_headers);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int HttpRequest::PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload)
  {
   if(m_headers == "")
      m_headers = "Content-Type: application/json\r\n";
   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);
   return WebRequest("POST", url, m_headers, m_timeout, data, result, result_headers);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int HttpRequest::ProcessResponse(int res, string &out, uchar &result[])
  {
   if(res >= 200 && res <= 299)
      return responseProcessor.ProcessSuccessResponse(out, result);

   return responseProcessor.ProcessErrorResponse(res, out, result);
  }



//+------------------------------------------------------------------+
//| Classe concreta para HttpResponseProcessor                       |
//+------------------------------------------------------------------+
class HttpResponseProcessor : public HttpResponseProcessorBase
  {
public:
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) override;
   virtual int       ProcessSuccessResponse(string &out, uchar &result[]) override;
   virtual int       ProcessErrorResponse(int res, string &out, uchar &result[]) override;
   virtual int       DetectAndSkipBOM(uchar &result[], int size) override;
  };
//+------------------------------------------------------------------+
//| Implementação das funções da classe HttpResponseProcessor       |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::ProcessResponse(int res, string &out, uchar &result[])
  {
   if(res >= 200 && res <= 299)
      return ProcessSuccessResponse(out, result);

   return ProcessErrorResponse(res, out, result);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::ProcessSuccessResponse(string &out, uchar &result[]) override
  {
   int size = ArraySize(result);
   int start_index = DetectAndSkipBOM(result, size);
   out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
   return 0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::ProcessErrorResponse(int res, string &out, uchar &result[]) override
  {
   ResetLastError();
   if(res == -1)
      return GetLastError();
   else
      if(res >= 100 && res <= 511)  // Erros HTTP
        {
         out = CharArrayToString(result);
         Print(out);
         return res;
        }
   return res;
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::DetectAndSkipBOM(uchar &result[], int size) override
  {
   int start_index = 0;
   for(int i = 0; i < MathMin(size, 3); i++)
     {
      if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
         start_index = i + 1;
      else
         break;
     }
   return start_index;
  };
//+------------------------------------------------------------------+


Ejemplos de uso de las clases

En esta sección, presentamos ejemplos prácticos de cómo utilizar las clases creadas para realizar solicitudes HTTP en MQL5. Estos ejemplos son importantes para ilustrar la reutilización de código y la eficiencia en la creación de nuevas funcionalidades.

Prueba de la respuesta de éxito:

void TestProcessSuccessResponse()
{
    HttpResponseProcessor processor;
    string output;
    uchar result[];

    // Simulando uma resposta de sucesso em formato JSON
    string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
    StringToCharArray(mockResponse, result);

    // Processando a resposta simulada
    processor.ProcessSuccessResponse(output, result);

    // Verificando a saída
    Print("Teste de Sucesso: ", output);
}

Explicación:

  • HttpResponseProcessor processor: Creación de un objeto processor de la clase HttpResponseProcessor.
  • StringToCharArray: Convierte la cadena de respuesta simulada en un array de caracteres.
  • processor.ProcessSuccessResponse(output, result): Llama al método para procesar la respuesta simulada.

Prueba de la respuesta de error:

void TestProcessErrorResponse()
{
    HttpResponseProcessor processor;
    string output;
    uchar result[];

    // Simulando uma resposta de erro (404 Not Found)
    string mockResponse = "404 Not Found";
    StringToCharArray(mockResponse, result);

    // Processando a resposta de erro
    processor.ProcessErrorResponse(404, output, result);

    // Verificando a saída
    Print("Teste de Erro: ", output);
}

Explicación:

  • Este ejemplo es similar al anterior, pero se enfoca en simular y procesar una respuesta de error HTTP.

Prueba de detección y eliminación del BOM:

void TestDetectAndSkipBOM()
{
    HttpResponseProcessor processor;
    uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'}; // 'abc' com BOM UTF-8

    // Detectando e pulando o BOM (Byte Order Mark)
    int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));

    // Verificando o índice inicial após o BOM
    Print("Índice de início após BOM: ", startIndex); // Esperado: 3
}

Explicación:

  • uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'};: Crea un array con el BOM UTF-8 seguido por 'abc'.
  • processor.DetectAndSkipBOM(result, ArraySize(result));: Detecta y omite el BOM, y devuelve el índice del inicio del contenido relevante.

Ejecución de pruebas y solicitud HTTP GET:

int OnInit()
{
    RunTests(); // Executa os testes

    HttpRequest httpRequest("", 5000); // Cria uma instância da classe HttpRequest
    string output; // Variável para armazenar a saída

    //Realizando um Pedido GET
    int responseCode = httpRequest.Request("GET", output, "https://jsonplaceholder.typicode.com/posts/1");

    // Exibindo o resultado
    Print("Código de Resposta: ", responseCode);
    Print("Saída: ", output);
}

Explicación:

  • HttpRequest httpRequest("", 5000): Crea un objeto httpRequest de la clase HttpRequest con configuraciones estándar.
  • httpRequest.Request("GET", output, "https://..."): Realiza una solicitud GET al URL proporcionado y almacena la respuesta en la variable output.

Estos ejemplos muestran cómo las clases HttpResponseProcessor y HttpRequest pueden ser utilizadas para manejar diferentes aspectos de las respuestas HTTP, como éxito, error y la presencia de BOM. Además, demuestran la facilidad de realizar solicitudes GET con la clase HttpRequest.

La modularización del código en clases es un enfoque fundamental en la programación que permite la creación de un sistema organizado y comprensible. Esta práctica implica la división del código en unidades independientes llamadas clases, cada una con su propia responsabilidad y funcionalidad específica.

Al adoptar esta técnica, los desarrolladores pueden estructurar su código de manera más lógica y clara, haciéndolo más legible y fácil de comprender. Esto significa que, en lugar de lidiar con un código monolítico y desorganizado, el desarrollador trabaja con pequeñas partes del sistema, cada una representada por una clase.

La ventaja de esto es que las clases pueden ser diseñadas de forma cohesiva, con métodos y atributos relacionados agrupados juntos. Esto no solo hace que el código sea más comprensible, sino que también simplifica el mantenimiento y el desarrollo futuro, ya que es más fácil localizar y corregir problemas en unidades individuales.

Además, la modularización en clases fomenta la reutilización de código, ya que las clases pueden ser utilizadas en diferentes partes del programa, con lo que se ahorra tiempo y esfuerzo en la creación de funcionalidades similares.

Ahora vamos a presentar un ejemplo completo, incluyendo el código de prueba para demostrar la aplicación práctica de las clases HttpResponseProcessor y HttpRequest. Este ejemplo ayudará a ilustrar cómo las clases pueden ser efectivamente utilizadas para realizar solicitudes HTTP y procesar respuestas, ya sea en un éxito o en un error, con lo cual se ofrece una visión detallada y amplia del funcionamiento del código.

//+------------------------------------------------------------------+
//|                                                         test.mq5 |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.00"

#include "Requests.mqh"


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void TestProcessSuccessResponse()
  {
   HttpResponseProcessor processor;
   string output;
   uchar result[];

// Simular uma resposta de sucesso (exemplo com JSON)
   string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
   StringToCharArray(mockResponse, result);

// Chamar ProcessSuccessResponse
   processor.ProcessSuccessResponse(output, result);

// Verificar se a saída é como esperado
   Print("Teste de Sucesso: ", output);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void TestProcessErrorResponse()
  {
   HttpResponseProcessor processor;
   string output;
   uchar result[];

// Simular uma resposta de erro (exemplo com erro 404)
   string mockResponse = "404 Not Found";
   StringToCharArray(mockResponse, result);

// Chamar ProcessErrorResponse com um código de erro simulado
   processor.ProcessErrorResponse(404, output, result);

// Verificar se a saída é como esperado
   Print("Teste de Erro: ", output);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void TestDetectAndSkipBOM()
  {
   HttpResponseProcessor processor;
   uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'}; // 'abc' com BOM UTF-8

// Chamar DetectAndSkipBOM
   int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));

// Verificar se o índice de início está correto
   Print("Índice de início após BOM: ", startIndex); // Esperado: 3
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void RunTests()
  {
   TestProcessSuccessResponse();
   TestProcessErrorResponse();
   TestDetectAndSkipBOM();
  }


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
// Executar testes HttpResponseProcessor
   RunTests();

// Criação da instância da classe HttpRequest
   HttpRequest httpRequest("", 5000);

// Variáveis para armazenar a saída
   string output;

// Realizando o pedido GET
   int responseCode = httpRequest.Request("GET", output, "https://jsonplaceholder.typicode.com/posts/1");

// Exibindo o resultado
   Print("Código de Resposta: ", responseCode);
   Print("Saída: ", output);


//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+


Conclusión

Hemos llegado al final de este artículo donde exploramos la transformación de un proyecto tradicional a un diseño orientado a objetos. El cambio del código de una estructura procedimental a una arquitectura basada en clases ofrece no solo una organización más clara, sino también abre camino para un mantenimiento y expansión del código más eficientes.

Relevancia de la programación orientada a objetos en MQL5:

  • La adopción del paradigma de la programación orientada a objetos (POO) representa un salto cualitativo significativo en el desarrollo de software. En nuestro contexto, donde el MQL5 se usa principalmente para trading algorítmico y automatización de estrategias en el mercado financiero, la importancia de un código bien estructurado y modular es aún más crucial.

Ventajas de la modularidad y encapsulamiento:

  • La organización del código en clases nos permite encapsular funcionalidades específicas, para hacer el sistema más intuitivo y fácil de mantener. Cada clase se convierte en un módulo con responsabilidades definidas, lo que simplifica la identificación y resolución de problemas, además de facilitar la expansión del sistema con nuevas funcionalidades.

Beneficios de la reutilización de código:

  • La POO incentiva la reutilización de código. Con la creación de clases bien definidas, podemos reutilizar estas estructuras en diferentes partes del proyecto o incluso en otros proyectos. Esto no solo ahorra tiempo, sino que también aumenta la coherencia y la fiabilidad del código.

Facilidad de mantenimiento y escalabilidad:

  • Mantener y escalar un proyecto se vuelve mucho más viable con la POO. A medida que su proyecto crece o se adapta a nuevos requisitos, la capacidad de modificar un componente específico sin afectar el resto del sistema es una ventaja invaluable.

Animo a todos los lectores, independientemente del nivel de experiencia en programación, a aplicar los conceptos de POO en sus propios proyectos MQL5. La transición a la POO puede parecer desafiante al principio, pero los beneficios a largo plazo en términos de calidad del código, eficiencia de desarrollo y facilidad de mantenimiento son innegables.

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

Desarrollo de un sistema de repetición (Parte 41): Inicio de la segunda fase (II) Desarrollo de un sistema de repetición (Parte 41): Inicio de la segunda fase (II)
Si hasta ahora todo te ha parecido correcto, significa que no estás pensando realmente a largo plazo. Donde empiezas a desarrollar aplicaciones y, con el tiempo, ya no necesitas programar nuevas aplicaciones. Solo tienes que conseguir que trabajen juntos. Veamos cómo terminar de montar el indicador del ratón.
Desarrollo de un sistema de repetición (Parte 40): Inicio de la segunda fase (I) Desarrollo de un sistema de repetición (Parte 40): Inicio de la segunda fase (I)
Esta es la nueva fase del sistema de repetición/simulación. En esta etapa, la conversación será realmente una conversación, y el contenido se volverá bastante denso. Les insto a leer el artículo con atención y a utilizar siempre las referencias que se proporcionen. Esto les ayudará a comprender mejor lo que se les está explicando.
Algoritmos de optimización de la población: Algoritmo de gotas de agua inteligentes (Intelligent Water Drops, IWD) Algoritmos de optimización de la población: Algoritmo de gotas de agua inteligentes (Intelligent Water Drops, IWD)
El artículo analiza un interesante algoritmo, las gotas de agua inteligentes, IWD, presente en la naturaleza inanimada, que simula el proceso de formación del cauce de un río. Las ideas de este algoritmo han permitido mejorar significativamente el anterior líder de la clasificación, el SDS, y el nuevo líder (SDSm modificado); como de costumbre, se puede encontrar en el archivo del artículo.
Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 3): Creación de jugadas automáticas y scripts de prueba en MQL5 Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 3): Creación de jugadas automáticas y scripts de prueba en MQL5
Este artículo explora la implementación de jugadas automáticas en el juego del tres en raya de Python, integrado con funciones de MQL5 y pruebas unitarias. El objetivo es mejorar la interactividad del juego y asegurar la robustez del sistema a través de pruebas en MQL5. La exposición cubre el desarrollo de la lógica del juego, la integración y las pruebas prácticas, y finaliza con la creación de un entorno de juego dinámico y un sistema integrado confiable.