Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD
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".
“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)
"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.
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
- home/machine01/#
- 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 |
Fig. 1. 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
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
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
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
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
Para obtener la dirección IP WSL, ejecutaremos el siguiente comando.
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
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
En los registros de Mosquitto veremos lo siguiente.
Fig. 10. 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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso