English Русский 中文 Deutsch 日本語 Português
preview
Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD

Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD

MetaTrader 5Integración | 31 octubre 2023, 17:29
359 1
Jocimar Lopes
Jocimar Lopes

Introducción

"...la estrategia es, sin duda: primero que funcione, luego que funcione bien y, por último, que sea rápido". Stephen C. Johnson y Brian W. Kernighan's, "The C Language and Models for Systems Programming" en Byte magazine, agosto 1983.

El intercambio de datos en tiempo real entre dos o más ejemplares de MetaTrader es una necesidad común entre los tráders y administradores de cuentas. Este tema es especialmente relevante al copiar transacciones de otra cuenta, pero también es importante para resolver problemas como el intercambio de información sobre cuentas, así como el análisis comparativo de símbolos y datos estadísticos para el aprendizaje automático. Podemos obtener la funcionalidad necesaria usando sockets de red, comunicación entre procesos con canalizaciones con nombre, servicios web, intercambio de archivos local y posiblemente algunas otras soluciones.

Como es típico en el desarrollo de software, cada una de estas soluciones tiene sus propias ventajas y desventajas en cuanto a usabilidad, estabilidad, fiabilidad y recursos necesarios para el desarrollo y mantenimiento. En pocas palabras, cada solución representa una relación costo-beneficio distinta según los requisitos y el presupuesto del usuario.

En el presente artículo hablaremos de los primeros pasos en la implementación del lado cliente del protocolo MQTT, una tecnología diseñada específicamente para satisfacer esta necesidad (intercambio de datos entre máquinas en tiempo real) con alto rendimiento, bajo consumo de tráfico, bajos requisitos de recursos y bajos costos.


¿Qué es el MQTT?

"El MQTT es un protocolo de intercambio de datos basado en el principio publicación-suscripción." Es ligero, abierto, simple y está diseñado para implementarse con facilidad, Estas características permiten su uso de forma efectiva en muchas situaciones, incluidos los entornos con recursos limitados, como las comunicaciones de máquina a máquina (M2M) y de Internet de las cosas (IoT), donde se requiere una pequeña cantidad de código y/o suficiente ancho de banda de red".

La definición anterior está tomada de la documentación de OASIS, el propietario y desarrollador del protocolo como estándar abierto desde 2013.

“En 2013, IBM presentó MQTT v3.1 al organismo de especificación de OASIS con un estatuto que permitía solo cambios menores en la especificación. Asumiendo la responsabilidad de mantener el estándar de IBM, OASIS lanzó la versión 3.1.1 el 29 de octubre de 2014. El 7 de marzo de 2019, se lanzó una actualización más importante de la versión 5 de MQTT que incluye varias características nuevas" (Wikipedia en inglés)

IBM comenzó a desarrollar el protocolo en 1999 para satisfacer la necesidad industrial de monitorear oleoductos usando sensores y enviando los datos vía satélite a centros de control remoto. Según Arlen Nipper, coautor del protocolo con el Dr. Andy Stanford-Clark, el objetivo era proporcionar un flujo de datos en tiempo real a estos centros.

"Intentamos utilizar el middleware de IBM de la época, el MQ Integrator. Mi trabajo en la industria de las comunicaciones en ese momento consistía en unir líneas conmutadas de 1200 y 300 baudios a una estación VSAT con una capacidad muy limitada".

Aunque el protocolo debía ser fiable, rápido y económico debido a las limitaciones tecnológicas y los altos costos de la red, debía ofrecer una entrega de datos de alta calidad con un conocimiento continuo de la sesión que le permitiera hacer frente a conexiones a Internet poco fiables o incluso intermitentes.

Siendo un protocolo binario, MQTT es muy eficiente en términos de memoria y requisitos de procesamiento: curiosamente, ¡el paquete MQTT más pequeño tiene solo dos bytes!

Como el protocolo opera según el criterio de publicación/suscripción (pub/sub), es bidireccional, a diferencia de los protocolos de solicitud/respuesta. En otras palabras, una vez establecida una conexión cliente/servidor, los datos se pueden transferir de cliente a servidor y de servidor a cliente en cualquier momento sin necesidad de una solicitud previa, a diferencia de HTTP WebRequest. Tan pronto como llegan los datos, el servidor los reenvía de forma inmediata a los destinatarios. Esta característica supone la piedra angular de las comunicaciones en tiempo real porque permite una latencia mínima entre los puntos finales. Algunos proveedores afirman que la latencia es del orden de apenas unos milisegundos.

No importa el tipo, formato, códec o cualquier otra cosa en los datos, el MQTT es un tipo de datos invariante (independiente de los datos). El usuario puede enviar/recibir bytes sin formato, formatos de texto (XML, objetos JSON), búferes de protocolo, imágenes, fragmentos de vídeo, etc.

La mayoría de las interacciones entre el cliente y el servidor pueden ser asincrónicas, lo cual significa que el MQTT es escalable. La industria de Internet de las cosas (IdC) a menudo involucra miles o incluso millones de dispositivos conectados e intercambiando datos en tiempo real. 

Los mensajes entre puntos finales suelen estar cifrados, ya que el protocolo es compatible con TLS y tiene mecanismos integrados de autenticación y autorización.

No es sorprendente que el MQTT no sea solo un conjunto de especificaciones de alto nivel, sino también una tecnología ampliamente utilizada en varias industrias.

"Hoy en día, MQTT se utiliza en una amplia variedad de sectores como la industria automotriz, la manufactura, las telecomunicaciones, la industria del gas y el petróleo, etc." ().


Componentes principales

El modelo publicación/suscripción es un modelo de mensajería muy conocido. El cliente se conecta al servidor y publica un mensaje sobre un tema específico; después de esto, todos los clientes suscritos a este tema reciben los mensajes. Este es el mecanismo principal del modelo. 

El servidor actúa como intermediario entre los clientes para recibir tanto suscripciones como publicaciones. El TCP/IP es el protocolo de transporte básico, mientras que los clientes son cualquier dispositivo que comprenda TCP/IP y MQTT. Por lo común, el mensaje suele ser una carga útil JSON o XML, pero puede ser cualquier cosa, incluida una secuencia de bytes sin formato.

El tema supone una cadena codificada en UTF-8 que se utiliza para describir una estructura jerárquica, similar a un espacio de nombres.

  • office/machine01/account123456

  • office/machine02/account789012

  • home/machine01/account345678

También podemos usar hash (#) como comodín para suscribirnos a un tema. Por ejemplo, para seguir desde casa todas las cuentas de machine01:
  • home/machine01/# 
O suscribirnos a todas las máquinas desde la oficina:
  • office/# 

Bien, el MQTT fue diseñado para la comunicación de máquina a máquina: se utiliza ampliamente en el Internet de las cosas y es fiable, rápido y económico. Pero usted se preguntará: ¿qué beneficio puede aportar al trading? ¿Cuáles son los posibles usos de MQTT en MetaTrader?

Como hemos mencionado antes, el caso de uso más obvio de MQTT en un entorno comercial es el copiado de operaciones, pero también se puede pensar en abastecer el aprendizaje automático con datos en tiempo real, cambiar el comportamiento del asesor según los datos en tiempo real recibidos de un servicio web o gestionar MetaTrader de forma remota desde cualquier dispositivo.

Podemos pensar en utilizar MQTT en cualquier escenario donde se requiera un flujo de datos en tiempo real entre máquinas.


Usando MQTT en MetaTrader

Existen bibliotecas de cliente de código abierto gratuitas para los lenguajes de propósito general más populares, incluidas las variantes correspondientes para dispositivos móviles e integrados. Entonces, para usar MQTT desde MQL5, podremos generar e importar la DLL correspondiente desde C, C++ o C#. 

Si los datos que se compartirán se limitan a transacciones/información de la cuenta y aceptamos una latencia relativamente alta, podemos utilizar una biblioteca cliente Python MQTT y un módulo Python como "puente". 

Pero, como ya sabemos, el uso de DLL tiene algunas consecuencias negativas para el entorno MQL5; la más notable es que el Mercado no acepta asesores expertos que dependan de DLL. Además, dichos asesores tienen prohibido ejecutar la optimización con backtesting usando la historia en la nube MQL5. Para evitar la dependencia de las DLL y el puente Python, la solución ideal sería desarrollar nuestra propia biblioteca de cliente MQTT para MetaTrader.

Y esto es precisamente lo que haremos en un futuro próximo: implementaremos el protocolo MQTT-v5.0 en el lado del cliente para MetaTrader 5.

La implementación de un cliente MQTT puede considerarse "relativamente simple" en comparación con otros protocolos de red, pero "relativamente fácil" no significa necesariamente que todo salga bien. Así que comenzaremos con un enfoque "ascendente", a saber, con un desarrollo basado en pruebas (TDD). Esperamos recibir comentarios y resultados de pruebas por parte de los miembros de la comunidad.

Si bien TDD puede usarse (y a menudo se usa) como una palabra de moda más para indicar casi cualquier cosa, la metodología funciona muy bien cuando tenemos un conjunto de especificaciones formalizadas, que es exactamente lo que hace un protocolo de red estandarizado.

Adoptando un enfoque ascendente, podremos hacer frente al aumento de los requisitos técnicos dividiéndolos en pequeños pasos. Los requisitos para MQTT no son tan estrictos, mientras que la parte del cliente es más simple en comparación con la parte del bróker. No obstante, aquí surgen algunas dificultades, especialmente desde la versión 5.0, que tiene algunas características adicionales.

Como nuestro tiempo no está remunerado y no disponemos de un gran equipo, el enfoque con pequeños pasos parece más apropiado aquí: ¿cómo envío un mensaje? ¿Qué debería de escribir? ¿Cómo puedo hacer que algo funcione, luego hacerlo funcionar bien y finalmente hacerlo funcionar rápidamente?


Requisitos mayores, pasos más pequeños: desglosando el protocolo MQTT

Como ocurre con la mayoría (si no todos) los protocolos de red, el MQTT opera dividiendo los datos transmitidos en lo que se denomina paquetes. Así, si el destinatario sabrá qué significa cada tipo de paquete y podrá adoptar el comportamiento operativo correcto según el tipo de paquete recibido. En el caso de MQTT, el tipo de paquete se llamará Control Packet Type y puede tener hasta tres partes:

  • El encabezado fijo está presente en todos los paquetes.

  • El encabezado variable está presente en algunos de ellos.

  • La carga útil también está presente solo en algunos paquetes.

Existen quince tipos de paquetes de control en MQTT-v5.0:

Tabla 1. Tipos de paquetes de control MQTT (tabla de la especificación OASIS)

Nombre Valor Dirección del flujo Descripción
Reserved
0 Prohibido
Reservado
CONNECT 1 Cliente-servidor Solicitud de conexión
CONNACK 2 Cliente-servidor Confirmación de conexión
PUBLISH 3 Cliente-servidor o servidor-cliente Publicar un mensaje
PUBREC 5 Cliente-servidor o servidor-cliente

Publicación recibida (entrega de QoS 2, parte 1)
PUBREL 6 Cliente-servidor o servidor-cliente
Lanzamiento de la publicación (entrega de QoS 2, parte 2)
PUBCOMP 7 Cliente-servidor o servidor-cliente

Publicación completa (entrega de QoS 2, parte 3)
SUBSCRIBE 8 Cliente-servidor Solicitud de suscripción
SUBACK 9 Cliente-servidor Confirmación de suscripción
UNSUBSCRIBE 10 Cliente-servidor Solicitud de cancelación
UNSUBACK 11 Cliente-servidor
Confirmación de cancelación de suscripción
PINGREQ 12 Cliente-servidor Solicitud PING
PINGRESP 13 Cliente-servidor Respuesta PING
DISCONNECT 14 Cliente-servidor o servidor-cliente Notificación sobre desconexión
AUTH 15 Cliente-servidor o servidor-cliente Autenticación de intercambio

El encabezado fijo de todos los paquetes de control posee el mismo formato.

Fig. 1. Formato de encabezado fijo MQTT

Formato de encabezado fijo MQTT

Porque no podemos hacer nada hasta que tengamos una conexión entre nuestro Cliente y el Servidor, y dado que hay una declaración clara en el estándar que dice:

"Después de que el Cliente establece una conexión de red con el Servidor, el primer paquete enviado desde el Cliente al Servidor DEBE ser un paquete de CONEXIÓN". 

Veamos cómo se debe formatear el encabezado fijo del paquete CONNECT.

Figura 2. Formato de encabezado fijo MQTT para el paquete CONNECT

Formato de encabezado fijo MQTT para el paquete CONNECT

Esto significa que deberemos llenarlo con dos bytes: el primer byte deberá tener el valor binario 00010000, mientras que el segundo deberá tener el valor de la llamada "longitud restante" ("Remaining Length").

La norma define la longitud restante como

"un número entero de bytes variable que representa el número de bytes restantes en el paquete de control actual, incluidos los datos del encabezado variable y la carga útil. La longitud restante no incluye los bytes usados para codificarla. El tamaño del paquete supone el número total de bytes en el paquete de control MQTT y será igual a la longitud del encabezado fijo más la longitud restante" (destacada por nosotros).

El estándar también define un esquema de codificación para un número entero de bytes variable.

“Un número entero de bytes variable se codifica utilizando un esquema de codificación que usa un byte para valores hasta 127. Los valores grandes se manejan de la siguiente manera: los siete bits menos significativos de cada byte codifican los datos, mientras que el bit más significativo se utiliza para indicar si hay más bytes en la representación. Así, cada byte codifica 128 valores y un "bit de continuación". El número máximo de bytes en un campo de enteros de bytes variables es cuatro. El valor codificado DEBERÁ utilizar el número mínimo de bytes necesarios para representar el valor."

Como podemos ver, estos párrafos contienen una gran cantidad de información útil. ¡Y estamos llenando solo el segundo byte!

Afortunadamente, el estándar ofrece "un algoritmo para codificar un número entero no negativo (X) en un esquema de codificación de enteros de bytes variables".

do
   encodedByte = X MOD 128
   X = X DIV 128
   // if there are more data to encode, set the top bit of this byte
   if (X > 0)
      encodedByte = encodedByte OR 128
   endif
   'output' encodedByte
while (X > 0)

"Donde MOD es el operador de módulo (% en C), DIV es la división de enteros (/ en C) y OR es la división bit a bit (| en C)".

Ahora tenemos:

  • la lista de todos los tipos de paquetes de control, 

  • el formato de encabezado de paquete CONNECT fijo con dos bytes,

  • el valor del primer byte,

  • y el algoritmo para codificar un entero de bytes variable que rellenará el segundo byte.

Ya podemos empezar a preparar nuestra primera prueba. 

Como estamos adoptando un enfoque TDD ascendente, escribiremos las pruebas antes de la implementación. Inicialmente 1) escribiremos pruebas que no funcionan, luego 2) escribiremos solo el código necesario para pasar la prueba y después 3) realizaremos la refactorización según sea necesario. No importa si la implementación original resulta ingenua, fea o tiene un rendimiento deficiente. Nos ocuparemos de estos problemas cuando tengamos un código funcional. El rendimiento se encuentra al final de nuestra lista de tareas pendientes.

Bien, ahora abriremos el MetaEditor y crearemos el script TestFixedHeader con el siguiente contenido.

#include <MQTT\mqtt.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print(TestFixedHeader_Connect());
  }
//---
bool TestFixedHeader_Connect()
  {
   uchar content_buffer[]; //empty
//---
   uchar expected[2];
   expected[0] = 1; //pkt type
   expected[1] = 0; //remaining length
//---
   uchar fixed_header[];
//---
   GenFixedHeader(CONNECT, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return false;
     }
   return true;
  }

También crearemos el encabezado mqtt.mqh, donde comenzaremos a desarrollar nuestras funciones y lo rellenaremos con el código siguiente.
void GenFixedHeader(uint pkt_type, uchar& buf[], uchar& head[])
  {
   ArrayFree(head);
   ArrayResize(head, 2);
//---
   head[0] = uchar(pkt_type);
//---
//Remaining Length
   uint x;
   x = ArraySize(buf);
   do
     {
      uint encodedByte = x % 128;
      x = (uint)(x / 128);
      if(x > 0)
        {
         encodedByte = encodedByte | 128;
        }
      head[1] = uchar(encodedByte);
     }
   while(x > 0);
  }
//+------------------------------------------------------------------+
enum ENUM_PKT_TYPE
  {
   CONNECT      =  1, // Connection request
   CONNACK      =  2, // Connect acknowledgment
   PUBLISH      =  3, // Publish message
   PUBACK       =  4, // Publish acknowledgment (QoS 1)
   PUBREC       =  5, // Publish received (QoS 2 delivery part 1)
   PUBREL       =  6, // Publish release (QoS 2 delivery part 2)
   PUBCOMP      =  7, // Publish complete (QoS 2 delivery part 3)
   SUBSCRIBE    =  8, // Subscribe request
   SUBACK       =  9, // Subscribe acknowledgment
   UNSUBSCRIBE 	=  10, // Unsubscribe request
   UNSUBACK     =  11, // Unsubscribe acknowledgment
   PINGREQ      =  12, // PING request
   PINGRESP     =  13, // PING response
   DISCONNECT  	=  14, // Disconnect notification
   AUTH         =  15, // Authentication exchange
  };

Una vez ejecutemos el script, deberíamos ver lo siguiente en la pestaña Expertos:

Fig. 3. Encabezado fijo - Test superado

Output Test Fixed Header - True

Para asegurarnos de que nuestra prueba funcione, deberemos ver cuándo falla. Por lo tanto, le recomiendo encarecidamente cambiar el parámetro de entrada content_buffer y dejar la variable esperada sin cambios. En la pestaña Expertos deberíamos ver algo como esto que sigue:

Fig. 4. Encabezado fijo: prueba fallida

Output Test Fixed Header - Prueba fallada

En cualquier caso, asumiremos que nuestras pruebas actualmente no son fiables, al igual que nuestro código en el encabezado mqtt.mqh. No pasa nada. Justo estamos comenzando y, a medida que avancemos, tendremos la oportunidad de mejorar, aprender de nuestros errores y perfeccionar nuestras habilidades.

Ahora podremos repetir la función TestFixedHeader_Connect para otros tipos de paquetes. Así, ignoraremos aquellos que tengan dirección de flujo únicamente del servidor al cliente. Nos referimos a CONNACK, PUBACK, SUBACK, UNSUBACK y PINGRESP. Estos tipos y la respuesta de ping serán generados por el servidor: nos ocuparemos de ellos más tarde.

Para asegurarnos de que nuestras pruebas funcionen como esperábamos, deberemos incluir pruebas que fallen previsiblemente. Estas pruebas retornarán true si fallan.
#include <MQTT\mqtt.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print(TestFixedHeader_Connect());
   Print(TestFixedHeader_Connect_RemainingLength1_Fail());
   Print(TestFixedHeader_Publish());
   Print(TestFixedHeader_Publish_RemainingLength1_Fail());
   Print(TestFixedHeader_Puback());
   Print(TestFixedHeader_Puback_RemainingLength1_Fail());
   Print(TestFixedHeader_Pubrec());
   Print(TestFixedHeader_Pubrec_RemainingLength1_Fail());
   Print(TestFixedHeader_Pubrel());
   Print(TestFixedHeader_Pubrel_RemainingLength1_Fail());
   Print(TestFixedHeader_Pubcomp());
   Print(TestFixedHeader_Pubcomp_RemainingLength1_Fail());
   Print(TestFixedHeader_Subscribe());
   Print(TestFixedHeader_Subscribe_RemainingLength1_Fail());
   Print(TestFixedHeader_Puback());
   Print(TestFixedHeader_Puback_RemainingLength1_Fail());
   Print(TestFixedHeader_Unsubscribe());
   Print(TestFixedHeader_Unsubscribe_RemainingLength1_Fail());
   Print(TestFixedHeader_Pingreq());
   Print(TestFixedHeader_Pingreq_RemainingLength1_Fail());
   Print(TestFixedHeader_Disconnect());
   Print(TestFixedHeader_Disconnect_RemainingLength1_Fail());
   Print(TestFixedHeader_Auth());
   Print(TestFixedHeader_Auth_RemainingLength1_Fail());
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Connect()
  {
   uchar content_buffer[]; //empty
//---
   uchar expected[2];
   expected[0] = 1; //pkt type
   expected[1] = 0; //remaining length
//---
   uchar fixed_header[];
//---
   GenFixedHeader(CONNECT, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return false;
     }
   Print(__FUNCTION__);
   return true;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Connect_RemainingLength1_Fail()
  {
   uchar content_buffer[]; //empty
   ArrayResize(content_buffer, 1);
   content_buffer[0] = 1;
//---
   uchar expected[2];
   expected[0] = 1; //pkt type
   expected[1] = 0; //remaining length should be 1
//---
   uchar fixed_header[];
//---
   GenFixedHeader(CONNECT, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return true;
     }
   Print(__FUNCTION__);
   return false;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Publish()
  {
   uchar content_buffer[]; //empty
//---
   uchar expected[2];
   expected[0] = 3; //pkt type
   expected[1] = 0; //remaining length
//---
   uchar fixed_header[];
//---
   GenFixedHeader(PUBLISH, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return false;
     }
   Print(__FUNCTION__);
   return true;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Publish_RemainingLength1_Fail()
  {
   uchar content_buffer[]; //empty
   ArrayResize(content_buffer, 1);
   content_buffer[0] = 1;
//---
   uchar expected[2];
   expected[0] = 3; //pkt type
   expected[1] = 0; //remaining length should be 1
//---
   uchar fixed_header[];
//---
   GenFixedHeader(PUBLISH, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return true;
     }
   Print(__FUNCTION__);
   return false;
  }
.
.
.
(final omitido para abreviar)

Veremos mucho código repetitivo y de copia y pega, 

pero esto traerá buenos rendimientos a largo plazo. Estas pruebas simples (incluso elementales) nos permitirán construir una especie de red de seguridad para nuestro desarrollo. Deberían servirnos de ayuda a la hora de 

  • mantenernos concentrados en la tarea en cuestión

  • evitar complicaciones excesivas

  • identificar errores de regresión.

Le recomiendo encarecidamente que escriba el código usted mismo en lugar de simplemente usar el archivo adjunto. Verá cuántos errores pequeños e "inofensivos" pueden detectarse desde el principio. A medida que profundicemos en las características de rendimiento de nuestros clientes, estas pruebas (y varias otras) demostrarán su valía. Asimismo, evitaremos un error técnico común: dejar la escritura de pruebas para más adelante. Generalmente, en este caso, nunca escribiremos dichas pruebas.

Fig. 5. Encabezado fijo - Todas las pruebas superadas

Output Test Fixed Header - Todas las pruebas superadas

Bien, veamos si nuestro encabezado CONNECT de dos bytes es reconocido como encabezado válido por un bróker MQTT


Cómo instalar un bróker (y cliente) MQTT para el desarrollo y las pruebas

Hay muchos brókeres MQTT de producción disponibles en Internet y la mayoría de ellos ofrecen algún tipo de URL de muestra para el desarrollo y las pruebas. Simplemente escriba "bróker MQTT" en un motor de búsqueda y verá muchas ofertas.

No obstante, nuestro cliente se encuentra actualmente en un estadio temprano de desarrollo. No podemos recibir ni leer una respuesta sin utilizar un rastreador de paquetes para interceptar el tráfico de la red. Esta herramienta nos resultará útil más adelante, pero por ahora bastará con tener un bróker MQTT que cumpla con las especificaciones para que podamos consultar sus registros y ver el resultado de nuestra interacción. Lo ideal sería instalarlo en una máquina virtual para tener una IP diferente a la de nuestro cliente, pues utilizando un bróker con una dirección IP diferente para el desarrollo y las pruebas, podremos resolver los problemas de conexión y autenticación más rápidamente.

Una vez más, existen varias opciones para Windows, Linux y Mac. Hemos instalado Mosquitto en el subsistema de Windows para Linux (WSL). Además de ser gratuito y de código abierto, Mosquitto viene con dos aplicaciones de línea de comandos muy útiles para el desarrollo: mosquitto_pub y mosquitto_sub para publicar y suscribirse a temas MQTT. También lo hemos instalado en una máquina de desarrollo en Windows para poder verificar algunos errores.

Recuerde que en MetaTrader debemos especificar cualquier URL externa en el menú " Servicio" > "Ajustes" > "Asesores Expertos" y que solo podamos acceder a los puertos 80 o 443 desde MetaTrader. Por lo tanto, si instalamos un bróker en WSL, deberemos asegurarnos de especificar nuestra dirección IP de host y también reenviar el tráfico de red que llega del puerto 80 al 1883, que es el puerto MQTT (y Mosquitto) predeterminado. La herramienta redir permite redireccionar de forma rápida y fiable.

Fig. 6. Diálogo MetaTrader 5 - Permitir URL de solicitud web

Diálogo de MetaTrader 5: Permitir URL de solicitud web


Para obtener la dirección IP WSL, ejecutaremos el siguiente comando.

Fig. 7. WSL - obtención del nombre de host

Fig. 7. WSL - obtención del nombre de host


Una vez instalado, Mosquitto se configurará automáticamente para ejecutarse como un "servicio" en la carga. De esta forma, simplemente cargaremos nuevamente WSL para ejecutar Mosquitto en el puerto 1883 predeterminado.

Para redirigir el tráfico de red desde el puerto 80 al 1883 usando redir, ejecutaremos el siguiente comando.

Fig. 8. Redirigiendo el tráfico de red usando redir

Redirigiendo puertos con la línea de comando redir


Finalmente, podemos verificar si un bróker MQTT que cumpla con las especificaciones reconoce nuestro encabezado CONNECT fijo de dos bytes como un encabezado válido. Luego crearemos un script temporal e insertaremos el siguiente código. (Recuerde cambiar la dirección IP en la variable broker_ip para que coincida con la salida del comando get hostname -I).

#include <MQTT\mqtt.mqh>

string broker_ip = "172.20.155.236";
int broker_port = 80;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int socket = SocketCreate();
   if(socket != INVALID_HANDLE)
     {
      if(SocketConnect(socket, broker_ip, broker_port, 1000))
        {
         Print("Connected ", broker_ip);
         //---
         uchar fixed_header[];
         uchar content_buffer[]; //empty
         //---
         GenFixedHeader(CONNECT, content_buffer, fixed_header);
         //---
         if(SocketSend(socket, fixed_header, ArraySize(fixed_header)) < 0)
           {
            Print("Failed sending fixed header ", GetLastError());
           }
        }
     }
  }

En la pestaña Expertos deberíamos ver algo como esto que sigue:

Fig. 9. Conexión con un bróker local

Conexión con un bróker local

En los registros de Mosquitto veremos lo siguiente.

Fig. 10. Conexión con un bróker local - registros de Mosquitto

Conexión con un bróker local - registros de Mosquitto

Mosquitto ha reconocido nuestro encabezado CONNECT fijo, pero el cliente desconocido (<unknown>) se ha desconectado de inmediato "debido a un error de protocolo". El error se ha producido porque aún no hemos incluido un encabezado de variable con el nombre del protocolo, el nivel del mismo y otros metadatos vinculados. Corregiremos esto en el siguiente paso.

Como podemos ver al principio del comando anterior, estamos usando el comando tail -f {pathToLogFile}. Podemos usarlo durante el desarrollo para monitorear las actualizaciones del registro de Mosquitto sin tener que abrir y cargar nuevamente el archivo.

En el siguiente paso, implementaremos el encabezado de la variable CONNECT (y otros) para mantener una conexión estable con nuestro bróker. También publicaremos (PUBLISH) un mensaje y trabajaremos con los paquetes CONNACK retornados por el bróker y sus correspondientes códigos de motivo (Reason Codes). El siguiente paso contendrá algunas operaciones bit a bit interesantes para completar nuestras banderas de conexión (Connect Flags). El siguiente paso también requerirá que mejoremos significativamente nuestras pruebas para hacer frente a las complejidades que surgirán de las interacciones entre cliente y bróker.


Conclusión

Este artículo ofrece una breve descripción general del protocolo de mensajería en tiempo real publicación-suscripción MQTT, sus orígenes y componentes principales. También hemos señalado algunos usos posibles de MQTT para la mensajería en tiempo real en un contexto comercial y cómo utilizarlo para operaciones automatizadas en MetaTrader 5, ya sea importando archivos DLL generados desde C, C++ o C#, o usando la biblioteca MQTT Python a través del módulo Python para MetaTrader 5.

Dadas las limitaciones impuestas al uso de DLL en MetaQuotes y MetaQuotes Cloud Tester, también hemos propuesto y descrito nuestros primeros pasos para implementar un cliente MQL5 MQTT nativo utilizando un enfoque de desarrollo basado en pruebas (TDD).


Enlaces útiles

No es necesario reinventar la rueda. Muchas soluciones a los problemas más comunes que enfrentan los desarrolladores al escribir clientes MQTT para otros lenguajes están disponibles como bibliotecas/SDK de código abierto.

  • Programas, incluidos brókeres, bibliotecas y herramientas.
  • Recursos relacionados con MQTT en GitHub.

Si usted es un desarrollador de MQL5 experimentado y tiene alguna sugerencia, deje un comentario a continuación.


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

Archivos adjuntos |
TestFixedHeader.mq5 (19.48 KB)
mqtt.mqh (2.19 KB)
pperea21
pperea21 | 1 nov. 2023 en 01:03
Buen artículo.

Hace unos meses hice la migración de la librería PubSubClient a MQL5.
https://github.com/gmag11/MQTT-MQL5-Library
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 20): FOREX (I) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 20): FOREX (I)
La intención inicial de este artículo no será cubrir todas las características de FOREX, sino más bien adaptar el sistema de manera que puedas realizar al menos una repetición del mercado. La simulación quedará para otro momento. Sin embargo, en caso de que no tengas los ticks y solo tengas las barras, con un poco de trabajo, puedes simular posibles transacciones que podrían haber ocurrido en FOREX. Esto será hasta que te muestre cómo adaptar el simulador. El hecho de intentar trabajar con datos provenientes de FOREX dentro del sistema sin modificarlo conlleva errores de rango.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 19): Ajustes necesarios Desarrollo de un sistema de repetición — Simulación de mercado (Parte 19): Ajustes necesarios
Lo que vamos a hacer aquí es preparar el terreno para que, cuando sea necesario agregar nuevas funciones al código, esto se haga de manera fluida y sencilla. El código actual aún no puede cubrir o manejar algunas cosas que serán necesarias para un progreso significativo. Necesitamos que todo se construya de manera que el esfuerzo de implementar algunas cosas sea lo más mínimo posible. Si esto se hace adecuadamente, tendremos la posibilidad de tener un sistema realmente muy versátil. Capaz de adaptarse muy fácilmente a cualquier situación que deba ser cubierta.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 21):  FOREX (II) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 21): FOREX (II)
Vamos a continuar el armado del sistema para cubrir el mercado FOREX. Entonces, para resolver este problema, primero necesitaríamos declarar la carga de los ticks antes de cargar las barras previas. Esto soluciona el problema, pero al mismo tiempo obliga al usuario a seguir un tipo de estructura en el archivo de configuración que, en mi opinión, no tiene mucho sentido. La razón es que, al desarrollar la programación responsable de analizar y ejecutar lo que está en el archivo de configuración, podemos permitir que el usuario declare las cosas en cualquier orden.
Desarrollo de un factor de calidad para los EAs Desarrollo de un factor de calidad para los EAs
En este artículo, te explicaremos cómo desarrollar un factor de calidad que tu Asesor Experto (EA) pueda mostrar en el simulador de estrategias. Te presentaremos dos formas de cálculo muy conocidas (Van Tharp y Sunny Harris).