English Русский Deutsch 日本語
preview
Creación de sistemas de trading basados en IA en MQL5 (Parte 2): Desarrollo de un programa integrado con ChatGPT y con interfaz de usuario

Creación de sistemas de trading basados en IA en MQL5 (Parte 2): Desarrollo de un programa integrado con ChatGPT y con interfaz de usuario

MetaTrader 5 — Sistemas comerciales |
26 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En nuestro artículo anterior (Parte 1), desarrollamos un framework para el procesamiento de JSON (JavaScript Object Notation) en MetaQuotes Language 5 (MQL5) mediante una clase que permite la serialización y deserialización de datos JSON para interactuar con las API (interfaces de programación de aplicaciones) de IA (inteligencia artificial). En la parte 2, crearemos un programa integrado con ChatGPT que cuenta con una interfaz de usuario, aprovechando el framework JSON para enviar prompts a la API de OpenAI y mostrar las respuestas en un gráfico de MetaTrader 5, lo que facilita la información y las observaciones interactivas para trading basadas en IA. Hoy abordaremos los siguientes temas:

  1. Comprensión del framework del programa de IA con ChatGPT
  2. Configuración del acceso a la API de OpenAI y de MetaTrader 5
  3. Implementación en MQL5
  4. Pruebas del programa ChatGPT
  5. Conclusión

Al final, tendrá un programa MQL5 funcional que integra ChatGPT para realizar consultas interactivas con IA, listo para mejorar sus estrategias de trading. ¡Manos a la obra!


Comprensión del framework del programa de IA con ChatGPT

El programa de IA con ChatGPT que pretendemos desarrollar integra las capacidades de un modelo de IA en MQL5, lo que nos permite interactuar con la IA enviando prompts y recibiendo análisis directamente en el gráfico de MetaTrader 5, sobre la base del framework JSON desarrollado en la parte anterior. Este programa ofrecerá una interfaz intuitiva para introducir consultas y visualizar las respuestas de la IA, y constituye un paso práctico hacia sistemas de trading basados en IA capaces de analizar los mercados o sugerir estrategias.

Nuestro plan consiste en crear un panel de control con un campo de entrada, un botón de envío y un área de visualización de respuestas, lo cual nos permitirá formular preguntas a la IA y ver las respuestas formateadas, al tiempo que nos aseguramos de que se registren todas las interacciones con fines de depuración. Además, configuraremos el programa para que se comunique con una API de IA, procese las respuestas y las muestre de forma clara, sentando así las bases para futuros programas que mejorarán el sistema con operaciones automatizadas basadas en los análisis de la IA. Aquí tiene una representación de lo que queremos conseguir.

FRAMEWORK DEL PROGRAMA

¡Vamos a configurar la API y MetaTrader 5!


Configuración del acceso a la API de OpenAI y de MetaTrader 5

Para que el programa de IA ChatGPT pueda comunicarse con la API de OpenAI, debemos obtener una clave de API válida y configurar MetaTrader 5 (MT5) para que permita las solicitudes HTTP, garantizando así una integración sin problemas. Le guiaremos en el proceso de obtención de una clave de API de OpenAI, en la verificación de su funcionamiento mediante pruebas con curl y en la configuración del terminal de trading para permitir el acceso a la URL (localizador uniforme de recursos) del punto de conexión de la API y garantizar así una comunicación fiable.

Cómo obtener una clave de API de OpenAI

Cree una cuenta de OpenAI: visite «platform.openai.com» y regístrese o inicie sesión en su cuenta. Vaya a la sección de la API y genere una nueva clave de API en el panel «API Keys». Copie la clave (por ejemplo, una que empiece por «sk-») de forma segura, ya que se utilizará en la configuración del programa.

Carga inicial:

PÁGINA DE INICIO

Generación de la clave secreta:

GENERACIÓN DE LA CLAVE SECRETA DE LA API

Proteja la clave de API: guárdela en un lugar seguro, ya que permite acceder a los servicios de OpenAI. Tenga en cuenta que pueden revocar la clave en caso de que se filtre.

Verificación de la clave API mediante pruebas con Curl

Para asegurarnos de que la clave de la API funciona correctamente, realizaremos una prueba con curl desde la línea de comandos para simular una solicitud a la API:

  • Instale Curl: asegúrese de que curl esté instalado en su sistema (está disponible de forma predeterminada en la mayoría de los sistemas Linux/macOS o se puede descargar para Windows).
  • Ejecute una solicitud de prueba: abra un terminal (Símbolo del sistema en Windows) y ejecute el siguiente comando curl, sustituyendo <YOUR_API_KEY> por su clave real de la API de OpenAI:

curl -X POST https://api.openai.com/v1/chat/completions -H "Authorization: Bearer <YOUR_API_KEY>" -H "Content-Type: application/json" -d "{\"model\":\"gpt-3.5-turbo\",\"messages\":[{\"role\":\"user\",\"content\":\"Test prompt\"}],\"max_tokens\":50}"

  • Verifique la respuesta: una respuesta correcta (HTTP 200) devuelve un objeto JSON con un array «choices» que contiene la respuesta de la IA (por ejemplo, «content»: «Esta es una respuesta de prueba»). Si la respuesta incluye un campo «error» (por ejemplo, clave de API no válida o cuota superada), solucione el problema comprobando la validez de la clave o el estado de facturación de su cuenta de OpenAI. Registre la respuesta para comprobar que todo funciona correctamente. Si la ejecución se ha realizado correctamente, debería obtener un resultado similar a este.

PRUEBA DE LA API CON CURL EN LA LÍNEA DE COMANDOS

Si este proceso le resulta complicado, puede crear un archivo de prueba sencillo y ejecutarlo en su navegador de la siguiente manera.

<!DOCTYPE html>
<html>
<head>
    <title>OpenAI API Test</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .container { border: 1px solid #ddd; padding: 20px; border-radius: 5px; }
        input, textarea, button { width: 100%; padding: 10px; margin: 5px 0; }
        textarea { height: 100px; }
        .response { background-color: #f5f5f5; padding: 15px; margin-top: 10px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>OpenAI API Test</h2>
        
        <div>
            <label for="apiKey">API Key:</label>
            <input type="text" id="apiKey" placeholder="Enter your API key">
        </div>
        
        <div>
            <label for="prompt">Prompt:</label>
            <textarea id="prompt" placeholder="Enter your prompt">Test prompt</textarea>
        </div>
        
        <div>
            <label for="maxTokens">Max Tokens:</label>
            <input type="number" id="maxTokens" value="50">
        </div>
        
        <button onclick="testAPI()">Test API</button>
        
        <div id="response" class="response" style="display: none;">
            <h3>Response:</h3>
            <pre id="responseContent"></pre>
        </div>
    </div>

    <script>
        async function testAPI() {
            const apiKey = document.getElementById('apiKey').value;
            const prompt = document.getElementById('prompt').value;
            const maxTokens = document.getElementById('maxTokens').value;
            
            if (!apiKey) {
                alert('Please enter your API key');
                return;
            }
            
            try {
                const response = await fetch('https://api.openai.com/v1/chat/completions', {
                    method: 'POST',
                    headers: {
                        'Authorization': `Bearer ${apiKey}`,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        model: 'gpt-3.5-turbo',
                        messages: [{role: 'user', content: prompt}],
                        max_tokens: parseInt(maxTokens)
                    })
                });
                
                const data = await response.json();
                
                // Display response
                document.getElementById('responseContent').textContent = JSON.stringify(data, null, 2);
                document.getElementById('response').style.display = 'block';
                
                // Check for errors
                if (data.error) {
                    console.error('API Error:', data.error);
                }
            } catch (error) {
                console.error('Error:', error);
                document.getElementById('responseContent').textContent = `Error: ${error.message}`;
                document.getElementById('response').style.display = 'block';
            }
        }
        
        // Pre-fill with your API key (optional - remove if you don't want this)
        document.getElementById('apiKey').value = 'sk-proj-fb... <YOUR_API_KEY>... X75...';

    </script>
</body>
</html>

Guárdelo como un archivo HTML (por ejemplo, api_test.html), ábralo en su navegador web y añada su clave. Debería ver la siguiente interfaz.

INTERFAZ DEL PROGRAMA

Al hacer clic en «Test API», debería obtener una respuesta como la siguiente.

RESPUESTA A LA PRUEBA

Configuración de MetaTrader 5 para la comunicación mediante API

Para que el programa pueda enviar solicitudes HTTP al punto de conexión de la API de OpenAI, configuraremos el terminal de trading de la siguiente manera:

  • Habilite las solicitudes web: Abra MT5, vaya a Herramientas > Opciones > Asesores Expertos y marque «Permitir WebRequest para las URL listadas».
  • Añada el punto de conexión de la API: En el campo «URL de WebRequest», añada «https://api.openai.com» para permitir las solicitudes a la API de OpenAI. Esto garantiza que la función WebRequest del programa pueda comunicarse sin que la configuración de seguridad de MT5 la bloquee.

Aquí tiene la visualización completa.

PUNTO DE CONEXIÓN DEL ENLACE PARA SOLICITUDES WEB DE MT5

Esta configuración permitirá que el programa envíe prompts y reciba respuestas de la IA, allanando el camino para su implementación. ¡Manos a la obra con la programación!


Implementación en MQL5

Para crear el programa en MQL5, abra MetaEditor, vaya al Navegador, busque la carpeta «Indicadores», haga clic en la pestaña «Nuevo» y siga las instrucciones para crear el archivo. Una vez creado el archivo, tendremos que declarar en el entorno de programación algunas variables globales y parámetros de entrada que utilizaremos a lo largo de todo el programa.

//+------------------------------------------------------------------+
//|                                             a. ChatGPT AI EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

//--- Input parameters
input string OpenAI_Model = "gpt-3.5-turbo";                                 // OpenAI Model
input string OpenAI_Endpoint = "https://api.openai.com/v1/chat/completions"; // OpenAI API Endpoint
input int MaxResponseLength = 500;                                           // Max length of ChatGPT response to display
input string LogFileName = "ChatGPT_EA_Log.txt";                             // Log file name
//--- Hardcoded API key (confirmed valid via curl test)
//--- Keep your API key private, for us, we added some parts so you can get the actual thing you should be having
string OpenAI_API_Key = "sk-proj-vjKCf ... <YOUR FULL ACTUAL API KEY HERE> ... jY..79n...";

Comenzaremos la implementación del programa, centrándonos en la configuración inicial de los parámetros de entrada para configurar la conexión del programa con la API de OpenAI. En primer lugar, debemos definir el parámetro de entrada «OpenAI_Model» como una cadena de caracteres con el valor «gpt-3.5-turbo», lo que especifica el modelo de ChatGPT que se utilizará para las solicitudes a la API y permite cambiar de modelo con flexibilidad si fuera necesario. Aquí puede seleccionar su modelo. A continuación, configuramos «OpenAI_Endpoint» como «https://api.openai.com/v1/chat/completions», definiendo así la URL para enviar solicitudes HTTP POST a la API de OpenAI.

A continuación, configuramos «MaxResponseLength» como un número entero con el valor 500, lo que limita la longitud de visualización de la respuesta de ChatGPT para garantizar que el resultado sea manejable en el gráfico de MetaTrader 5. Por último, definimos «LogFileName» como «ChatGPT_EA_Log.txt» para especificar el archivo en el que se registrarán las interacciones con la API y los errores, y asignamos directamente en el código a «OpenAI_API_Key» nuestra clave de API, cuya validez hemos confirmado (que comienza por «sk-proj-»), para autenticar las solicitudes, sentando así las bases de la funcionalidad del programa. Para el registro y la visualización de botones, añadimos las siguientes variables.

//--- Global variables
string conversationHistory = "";                             //--- Stores conversation history
int logFileHandle = INVALID_HANDLE;                          //--- Handle for log file
bool button_hover = false;                                   //--- Flag for button hover state
color button_original_bg = clrRoyalBlue;                     //--- Original button background color
color button_darker_bg;                                      //--- Darkened button background for hover

Además, hemos añadido comentarios a las variables para que se entiendan por sí mismas. Lo siguiente que debemos hacer es definir las funciones auxiliares que utilizaremos para crear la interfaz, pero antes, asegúrese de copiar y pegar la lógica de procesamiento de JSON de la parte anterior, ya que no queremos crear varios archivos. También podría crear el archivo e incluirlo en el programa, pero lo haremos a medida que el programa se vuelva más complejo. Por ahora, vamos a mantenerlo todo sencillo y claro.

//+------------------------------------------------------------------+
//| Creates a rectangle label object                                 |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xDistance, int yDistance, int xSize, int ySize,
                    color bgColor, int borderWidth, color borderColor = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT,
                    ENUM_LINE_STYLE borderStyle = STYLE_SOLID,
                    ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {   //--- Create rectangle label
   ResetLastError();                                         //--- Reset previous errors
   if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Attempt creation
      Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);       //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);       //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);   //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, borderWidth); //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Not background
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not pressed
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ChartRedraw(0);                                           //--- Redraw chart
   return (true);                                            //--- Success
}
//+------------------------------------------------------------------+
//| Creates a button object                                          |
//+------------------------------------------------------------------+
bool createButton(string objName, int xDistance, int yDistance, int xSize, int ySize,
                  string text = "", color textColor = clrBlack, int fontSize = 12,
                  color bgColor = clrNONE, color borderColor = clrNONE,
                  string font = "Arial Rounded MT Bold",
                  ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, bool isBack = false) { //--- Create button
   ResetLastError();                                         //--- Reset errors
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {    //--- Attempt creation
      Print(__FUNCTION__, ": failed to create the button! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);       //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);       //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);   //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);   //--- Set background
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, isBack);       //--- Set back
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not pressed
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ChartRedraw(0);                                           //--- Redraw
   return (true);                                            //--- Success
}
//+------------------------------------------------------------------+
//| Creates an edit field object                                     |
//+------------------------------------------------------------------+
bool createEdit(string objName, int xDistance, int yDistance, int xSize, int ySize,
                string text = "", color textColor = clrBlack, int fontSize = 12,
                color bgColor = clrNONE, color borderColor = clrNONE,
                string font = "Arial Rounded MT Bold",
                ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER,
                int align = ALIGN_LEFT, bool readOnly = false) {  //--- Create edit
   ResetLastError();                                         //--- Reset errors
   if (!ObjectCreate(0, objName, OBJ_EDIT, 0, 0, 0)) {      //--- Attempt creation
      Print(__FUNCTION__, ": failed to create the edit! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);       //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);       //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);   //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);   //--- Set background
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_ALIGN, align);       //--- Set alignment
   ObjectSetInteger(0, objName, OBJPROP_READONLY, readOnly); //--- Set read-only
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Not back
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not active
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ChartRedraw(0);                                           //--- Redraw
   return (true);                                            //--- Success
}
//+------------------------------------------------------------------+
//| Creates a text label object                                      |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xDistance, int yDistance,
                 string text, color textColor = clrBlack, int fontSize = 12,
                 string font = "Arial Rounded MT Bold",
                 ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER,
                 ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER) {   //--- Create label
   ResetLastError();                                         //--- Reset errors
   if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) {     //--- Attempt creation
      Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);   //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Not back
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not active
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor);     //--- Set anchor
   ChartRedraw(0);                                           //--- Redraw
   return (true);                                            //--- Success
}

Aquí creamos los elementos gráficos esenciales para que los operadores interactúen con el gráfico. En primer lugar, implementamos la función «createRecLabel», que utiliza ObjectCreate para dibujar una etiqueta rectangular (OBJ_RECTANGLE_LABEL) en las coordenadas especificadas («xDistance», «yDistance») con las dimensiones («xSize», «ySize»). Mediante OBJPROP_BGCOLOR se establece el color de fondo; también se configuran la anchura del borde, el color («OBJPROP_COLOR»), el tipo de borde («OBJPROP_BORDER_TYPE» como «BORDER_FLAT»), el estilo («OBJPROP_STYLE» como «STYLE_SOLID») y la esquina («OBJPROP_CORNER» como «CORNER_LEFT_UPPER»), utilizando ObjectSetInteger. La función garantiza que la etiqueta no se pueda seleccionar ni se muestre en segundo plano y utiliza ChartRedraw para actualizar el gráfico. Si la creación falla, devuelve «false» y registra el error mediante la función Print.

A continuación, desarrollamos la función «createButton», que crea un botón (OBJ_BUTTON) con parámetros de coordenadas y tamaño similares. Configura el texto (OBJPROP_TEXT), el color del texto («OBJPROP_COLOR»), el tamaño de la fuente, la fuente (Arial Rounded MT Bold), el color de fondo, el color del borde y la esquina mediante ObjectSetString y «ObjectSetInteger», con un indicador opcional para mostrarlo en segundo plano («isBack»). Garantiza que el botón no se pueda seleccionar ni esté pulsado y actualiza el gráfico. En caso de fallo, devuelve «false» y registra el error.

Luego implementamos la función «createEdit», que crea un campo de texto editable (OBJ_EDIT) con parámetros para el texto, la alineación («OBJPROP_ALIGN» como «ALIGN_LEFT»), el estado de solo lectura y otras propiedades de estilo similares, utilizando «ObjectSetString» y «ObjectSetInteger» para configurar la apariencia y el comportamiento, actualizando el gráfico y gestionando los errores de forma similar. Por último, añadimos la función «createLabel», que crea una etiqueta de texto (OBJ_LABEL) con el texto, el color, el tamaño de fuente, la fuente, la esquina y el punto de anclaje especificados («OBJPROP_ANCHOR» como «ANCHOR_LEFT_UPPER»), garantizando que no se pueda seleccionar y que el gráfico se actualice. Estas funciones constituyen la base gráfica del panel interactivo de nuestro programa, lo cual permite introducir datos y visualizar las interacciones con la IA. Queremos que haya un botón que se oscurezca al pasar el cursor por encima. Así, vamos a crear una función para eso.

//+------------------------------------------------------------------+
//| Darkens a given color by a factor                                |
//+------------------------------------------------------------------+
color DarkenColor(color colorValue, double factor = 0.8) {   //--- Darken color function
   int red = int((colorValue & 0xFF) * factor);              //--- Calculate darkened red component
   int green = int(((colorValue >> 8) & 0xFF) * factor);     //--- Calculate darkened green component
   int blue = int(((colorValue >> 16) & 0xFF) * factor);     //--- Calculate darkened blue component
   return (color)(red | (green << 8) | (blue << 16));        //--- Combine and return darkened color
}

Aquí implementamos la función «DarkenColor», que recibe un valor de color y un factor opcional (por defecto, 0,8) para reducir el brillo. Para calcular los componentes rojo, verde y azul oscurecidos, extrae cada uno mediante operaciones bit a bit («colorValue & 0xFF» para el rojo, «(colorValue >> 8) & 0xFF» para el verde y «(colorValue >> 16) & 0xFF» para el azul), los multiplica por el factor y los combina mediante desplazamientos y una operación OR bit a bit («red | (green << 8) | (blue << 16)») para devolver el nuevo color. Ahora creamos el panel interactivo para dejarlo todo listo.

//+------------------------------------------------------------------+
//| Creates the dashboard UI                                         |
//+------------------------------------------------------------------+
void CreateDashboard() {                                     //--- Create UI
   createEdit("ChatGPT_InputEdit", 20, 20, 400, 40, "", clrBlack, 12, clrWhiteSmoke, clrDarkGray, "Arial", CORNER_LEFT_UPPER, ALIGN_LEFT, false); //--- Input edit
   createButton("ChatGPT_SubmitButton", 430, 20, 100, 40, "Send", clrWhite, 12, button_original_bg, clrDarkBlue, "Arial", CORNER_LEFT_UPPER, false); //--- Submit button
   createRecLabel("ChatGPT_ResponseBg", 20, 70, 510, 300, clrWhite, 2, clrLightGray, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Response background
   ChartRedraw();                                            //--- Redraw
}

Luego implementamos la función «CreateDashboard» para crear la interfaz de usuario en el gráfico, lo que nos permite interactuar con la IA. En primer lugar, llamamos a «createEdit» para crear un campo de texto editable llamado «ChatGPT_InputEdit» en las coordenadas (20, 20), con unas dimensiones de 400 x 40 píxeles, texto negro, fuente Arial de 12 puntos, fondo blanco humo, borde gris oscuro y alineación a la izquierda. El modo de solo lectura está desactivado, lo que nos permite introducir prompts. Después utilizamos «createButton» para añadir un botón llamado «ChatGPT_SubmitButton» en (430, 20), con unas dimensiones de 100 x 40 píxeles y la etiqueta «Send» en texto blanco con fuente Arial de 12 puntos. Tiene un fondo azul real («button_original_bg») y un borde azul oscuro, y se sitúa tomando como referencia la esquina superior izquierda para enviar solicitudes a la API.

A continuación, llamamos a «createRecLabel» para crear un fondo de respuesta denominado «ChatGPT_ResponseBg» en la posición (20, 70) con unas dimensiones de 510 x 300 píxeles, con fondo blanco, un borde gris claro de 2 píxeles, de tipo plano y estilo sólido, lo que proporciona un área despejada para mostrar las respuestas de la IA. Por último, llamamos a ChartRedraw para actualizar el gráfico, asegurándonos de que todos los elementos de la interfaz de usuario estén visibles. Ahora podemos llamar a la función en el controlador de eventos OnInit para ver qué hemos conseguido.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {                                               //--- Initialization
   button_darker_bg = DarkenColor(button_original_bg);       //--- Set darker button color
   logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT); //--- Open log
   if(logFileHandle == INVALID_HANDLE) {                     //--- Check handle
      Print("Failed to open log file: ", GetLastError());    //--- Print error
      return(INIT_FAILED);                                   //--- Fail init
   }
   FileSeek(logFileHandle, 0, SEEK_END);                     //--- Seek end
   FileWriteString(logFileHandle, "EA Initialized at " + TimeToString(TimeCurrent()) + "\n"); //--- Log init
   CreateDashboard();                                        //--- Create UI
   return(INIT_SUCCEEDED);                                   //--- Success
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {                            //--- Deinit
   ObjectsDeleteAll(0, "ChatGPT_");                          //--- Delete objects
   if(logFileHandle != INVALID_HANDLE) {                     //--- Check handle
      FileClose(logFileHandle);                              //--- Close log
   }
}

En la función OnInit, llamamos a «DarkenColor» con «button_original_bg» para calcular un tono más oscuro para «button_darker_bg», lo que permite un efecto al pasar el cursor por encima del botón de envío. A continuación, abrimos el archivo de registro especificado por «LogFileName» mediante FileOpen con los indicadores de lectura, escritura y texto, y almacenamos el identificador en «logFileHandle»; si el identificador no es válido, registramos un error y devolvemos INIT_FAILED. Esto nos permitirá llevar un control de lo que hacemos en segundo plano.

Entonces, utilizamos «FileSeek» para desplazarnos hasta el final del archivo de registro y escribimos un mensaje de inicialización con FileWriteString, incluyendo la hora actual mediante la función TimeToString. Luego llamamos a «CreateDashboard» para configurar la interfaz de usuario y devolvemos INIT_SUCCEEDED para confirmar que la inicialización se ha realizado correctamente. En la función OnDeinit, realizamos una limpieza llamando a ObjectsDeleteAll para eliminar todos los objetos de gráfico cuyo nombre comience por «ChatGPT_» y, si «logFileHandle» es válido, cerramos el archivo de registro con la función FileClose. Durante todo este tiempo, mientras el programa está vinculado al gráfico, mantenemos el archivo abierto. Eso significa que no podemos abrirlo mientras el programa esté en ejecución. Podría optar por cerrar el archivo tras enviar un prompt y recibir una respuesta, pero no resulta práctico si estamos interactuando de forma activa. Al ejecutar el programa, obtenemos el siguiente resultado.

INTERFAZ DE USUARIO CREADA

Ahora que hemos creado la interfaz, tenemos que encontrar una forma de mostrar la conversación como un chat normal, pero el problema es que MQL5 no ofrece una forma directa de hacerlo. La función «label» por sí sola puede mostrar un texto de hasta 63 caracteres. Así pues, lo que tenemos que hacer es implementar un mecanismo de ajuste de texto que divida la conversación en varias etiquetas en lugar de una sola y limite el número de caracteres de cada etiqueta al máximo permitido. Esta es la lógica que seguimos para ello.

//+------------------------------------------------------------------+
//| Wraps text respecting newlines and max width                     |
//+------------------------------------------------------------------+
void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], int offset = 0) { //--- Wrap text function
   const int maxChars = 60;                                  //--- Max chars per line
   ArrayResize(wrappedLines, 0);                             //--- Clear output array
   TextSetFont(font, -fontSize * 10, 0);                     //--- Set font for measurement
   string paragraphs[];                                      //--- Array for paragraphs
   int numParagraphs = StringSplit(inputText, '\n', paragraphs); //--- Split by newline
   for (int p = 0; p < numParagraphs; p++) {                 //--- Loop paragraphs
      string para = paragraphs[p];                           //--- Get paragraph
      if (StringLen(para) == 0) continue;                    //--- Skip empty
      string words[];                                        //--- Array for words
      int numWords = StringSplit(para, ' ', words);          //--- Split by space
      string currentLine = "";                               //--- Current line builder
      for (int w = 0; w < numWords; w++) {                   //--- Loop words
         string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Test add word
         uint wid, hei;                                      //--- Width, height
         TextGetSize(testLine, wid, hei);                    //--- Get size
         int textWidth = (int)wid;                           //--- Cast width
         if (textWidth + offset <= maxWidth && StringLen(testLine) <= maxChars) { //--- Fits
            currentLine = testLine;                          //--- Update line
         } else {                                            //--- Doesn't fit
            if (StringLen(currentLine) > 0) {                //--- Add current if not empty
               int size = ArraySize(wrappedLines);           //--- Get size
               ArrayResize(wrappedLines, size + 1);          //--- Resize
               wrappedLines[size] = currentLine;             //--- Add line
            }
            currentLine = words[w];                          //--- Start new with word
            TextGetSize(currentLine, wid, hei);              //--- Get size
            textWidth = (int)wid;                            //--- Cast
            if (textWidth + offset > maxWidth || StringLen(currentLine) > maxChars) { //--- Word too long
               string wrappedWord = "";                      //--- Word builder
               for (int c = 0; c < StringLen(words[w]); c++) { //--- Char loop
                  string testWord = wrappedWord + StringSubstr(words[w], c, 1); //--- Test add char
                  TextGetSize(testWord, wid, hei);           //--- Get size
                  int wordWidth = (int)wid;                  //--- Cast
                  if (wordWidth + offset > maxWidth || StringLen(testWord) > maxChars) { //--- Char exceeds
                     if (StringLen(wrappedWord) > 0) {       //--- Add if not empty
                        int size = ArraySize(wrappedLines);  //--- Get size
                        ArrayResize(wrappedLines, size + 1); //--- Resize
                        wrappedLines[size] = wrappedWord;    //--- Add
                     }
                     wrappedWord = StringSubstr(words[w], c, 1); //--- New with char
                  } else {                                   //--- Fits
                     wrappedWord = testWord;                 //--- Update
                  }
               }
               currentLine = wrappedWord;                    //--- Set current
            }
         }
      }
      if (StringLen(currentLine) > 0) {                      //--- Add remaining line
         int size = ArraySize(wrappedLines);                 //--- Get size
         ArrayResize(wrappedLines, size + 1);                //--- Resize
         wrappedLines[size] = currentLine;                   //--- Add
      }
   }
}

Vamos a implementar la función «WrapText» para dar formato al texto que se muestra en el gráfico, con el fin de garantizar que las respuestas de la IA sean legibles. En primer lugar, definimos una constante «maxChars» con un valor de 60 para limitar la longitud de las líneas, aunque usted podría fijar el límite en 63; vaciamos el array de salida «wrappedLines» con ArrayResize y configuramos la fuente mediante TextSetFont con la fuente y el tamaño especificados (con el tamaño escalado mediante -fontSize * 10). A continuación, dividimos el texto de entrada en párrafos utilizando StringSplit con el salto de línea como delimitador y recorremos cada párrafo que no esté vacío. Dividimos cada párrafo en palabras utilizando de nuevo «StringSplit», pero esta vez con un espacio como delimitador, e inicializamos una variable «currentLine» vacía.

Luego iteramos las palabras y construimos «testLine» añadiendo cada palabra a «currentLine», con un espacio separador si «currentLine» no está vacía, y utilizamos TextGetSize para medir su anchura; si la anchura más el desplazamiento no supera «maxWidth» y la longitud es inferior a «maxChars», actualizamos «currentLine»; de lo contrario, añadimos «currentLine» a «wrappedLines» si no está vacía y comenzamos una nueva línea con la palabra actual. En el caso de las palabras demasiado largas, recorremos sus caracteres, construimos «wrappedWord» y comprobamos su anchura; añadimos «wrappedWord» a «wrappedLines» cuando su anchura supera «maxWidth» o su longitud supera «maxChars», y actualizamos «currentLine» con la parte restante de la palabra. Por último, añadimos cualquier «currentLine» restante a «wrappedLines», asegurándonos de que todo el texto esté formateado para que se muestre con claridad en el área de respuesta del programa. Ahora podemos aplicar esta lógica y actualizar nuestra pantalla con un texto de ejemplo en el que se explique al usuario cómo empezar.

//+------------------------------------------------------------------+
//| Updates the response display                                     |
//+------------------------------------------------------------------+
void UpdateResponseDisplay() {                               //--- Update display
   int total = ObjectsTotal(0, 0, -1);                       //--- Get total objects
   for (int j = total - 1; j >= 0; j--) {                    //--- Loop backwards
      string name = ObjectName(0, j, 0, -1);                 //--- Get name
      if (StringFind(name, "ChatGPT_ResponseLine_") == 0 || StringFind(name, "ChatGPT_MessageBg_") == 0) { //--- Check prefix
         ObjectDelete(0, name);                              //--- Delete
      }
   }
   string displayText = conversationHistory;                 //--- Get history
   if (displayText == "") {                                  //--- If empty
      string objName = "ChatGPT_ResponseLine_0";             //--- Name
      createLabel(objName, 30, 80, "Type your message above and click Send to chat with the AI.", clrGray, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Placeholder
      ChartRedraw();                                         //--- Redraw
      return;                                                //--- Exit
   }
   string font = "Arial";                                    //--- Font
   int fontSize = 10;                                        //--- Size
   int padding = 10;                                         //--- Padding
   int maxWidth = 510 - 2 * padding;                         //--- Max width
   string wrappedLines[];                                    //--- Wrapped lines
   WrapText(displayText, font, fontSize, maxWidth, wrappedLines, 0); //--- Wrap text
   TextSetFont(font, -fontSize * 10, 0);                     //--- Set font
   uint wid, hei;                                            //--- Size vars
   TextGetSize("A", wid, hei);                               //--- Get height
   int lineHeight = (int)hei;                                //--- Line height
   int responseHeight = 300;                                 //--- Response height
   int maxVisibleLines = (responseHeight - 2 * padding) / lineHeight; //--- Max lines
   int numLines = ArraySize(wrappedLines);                   //--- Num lines
   int startLine = MathMax(0, numLines - maxVisibleLines);   //--- Start line
   int textX = 20 + padding;                                 //--- Text x
   int textY = 70 + padding;                                 //--- Text y
   color currentColor = clrWhite;                            //--- Current color
   for (int i = startLine; i < numLines; i++) {              //--- Loop lines
      string line = wrappedLines[i];                         //--- Get line
      if (StringFind(line, "You: ") == 0) {                  //--- User color
         currentColor = clrGray;                             //--- Set gray
      } else if (StringFind(line, "AI: ") == 0) {            //--- AI color
         currentColor = clrBlue;                             //--- Set blue
      }
      string objName = "ChatGPT_ResponseLine_" + IntegerToString(i - startLine); //--- Name
      createLabel(objName, textX, textY, line, currentColor, fontSize, font, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create label
      textY += lineHeight;                                   //--- Next y
   }
   ChartRedraw();                                            //--- Redraw
}

Aquí implementamos la función «UpdateResponseDisplay» para mostrar dinámicamente el historial de la conversación. En primer lugar, utilizamos ObjectsTotal para contar todos los objetos del gráfico y realizamos una iteración hacia atrás, llamando a ObjectName para recuperar el nombre de cada objeto y a ObjectDelete para eliminar los objetos con los prefijos «ChatGPT_ResponseLine_» o «ChatGPT_MessageBg_», borrando así los elementos de visualización anteriores. A continuación, comprobamos si «conversationHistory» está vacío; si es así, llamamos a «createLabel» para mostrar un mensaje provisional en la posición (30, 80) en color gris, con fuente Arial de 10 puntos, en el que se pide a los usuarios que introduzcan un mensaje, y salimos tras volver a dibujar la pantalla.

Si «conversationHistory» contiene texto, establecemos la fuente en Arial, el tamaño de fuente en 10 y el relleno en 10, y calculamos «maxWidth» como 510 menos el doble del relleno; a continuación, llamamos a «WrapText» para dividir el texto en «wrappedLines». Luego utilizamos TextSetFont y TextGetSize para determinar la altura de línea, calculamos «maxVisibleLines» basándonos en una altura de respuesta de 300 píxeles y establecemos la línea inicial con MathMax para mostrar las líneas más recientes. Después iteramos «wrappedLines» a partir de «startLine», estableciendo «currentColor» en gris para las líneas que empiezan por «You: » o en azul para las que empiezan por «AI: », y llamamos a «createLabel» para mostrar cada línea en las coordenadas («textX», «textY»), incrementando «textY» en «lineHeight» para cada etiqueta, con nombres únicos como «ChatGPT_ResponseLine_0». Por último, llamamos a ChartRedraw para actualizar el gráfico, asegurándonos de que la conversación se muestre de forma clara y fácil de distinguir a simple vista. Cuando llamamos a esta función durante la inicialización, obtenemos el siguiente resultado.

PANTALLA ACTUALIZADA CON EL MENSAJE INICIAL

En la imagen podemos ver que hemos actualizado la pantalla con un mensaje inicial y que ya estamos listos para mostrar las conversaciones. Ahora creamos una función para obtener la respuesta a los prompts que enviemos, pero antes definamos las funciones auxiliares que vamos a necesitar.

//+------------------------------------------------------------------+
//| Escapes string for JSON                                          |
//+------------------------------------------------------------------+
string JsonEscape(string value) {                            //--- JSON escape
   StringReplace(value, "\\", "\\\\");                       //--- Escape backslash
   StringReplace(value, "\"", "\\\"");                       //--- Escape quote
   StringReplace(value, "\n", "\\n");                        //--- Escape newline
   StringReplace(value, "\r", "\\r");                        //--- Escape carriage
   StringReplace(value, "\t", "\\t");                        //--- Escape tab
   StringReplace(value, "\f", "\\f");                        //--- Escape form feed
   for(int i = 0; i < StringLen(value); i++) {               //--- Loop chars
      ushort charCode = StringGetCharacter(value, i);        //--- Get char
      if(charCode < 32 || charCode == 127) {                 //--- Control chars
         string hex = StringFormat("\\u%04x", charCode);     //--- Hex escape
         string before = StringSubstr(value, 0, i);          //--- Before part
         string after = StringSubstr(value, i + 1);          //--- After part
         value = before + hex + after;                       //--- Replace
         i += 5;                                             //--- Skip added
      }
   }
   return value;                                             //--- Return escaped
}
//+------------------------------------------------------------------+
//| Logs char array as hex for debugging                             |
//+------------------------------------------------------------------+
string LogCharArray(char &data[]) {                          //--- Log char array
   string result = "";                                       //--- Result string
   for(int i = 0; i < ArraySize(data); i++) {                //--- Loop array
      result += StringFormat("%02X ", data[i]);              //--- Append hex
   }
   return result;                                            //--- Return
}

Antes de crear la lógica propiamente dicha para gestionar las respuestas, implementamos funciones de utilidad para gestionar el escape de cadenas JSON y la depuración, con el fin de garantizar una comunicación robusta a través de la API. En primer lugar, implementamos la función «JsonEscape», que toma una cadena como entrada y escapa los caracteres especiales para que cumpla con el formato JSON utilizando StringReplace para convertir la barra invertida en «\\», las comillas en «\"», el salto de línea en «\n», el retorno de carro en «\r», la tabulación en «\t» y el salto de página en «\f»; a continuación, recorre cada carácter con StringGetCharacter, comprueba si es un carácter de control (código ASCII inferior a 32 o igual a 127) y lo sustituye por una secuencia de escape Unicode («\uXXXX») mediante StringFormat, actualizando la cadena con StringSubstr para concatenar las partes anteriores y posteriores al escape y ajustando el índice para omitir los caracteres añadidos; finalmente, devuelve la cadena escapada.

Entonces, desarrollamos la función «LogCharArray», que convierte un array de caracteres en una cadena hexadecimal para la depuración; para ello, inicializamos una cadena de resultado vacía, recorremos el array con ArraySize, añadimos el valor hexadecimal de cada carácter utilizando «StringFormat» con el formato «%02X» y devolvemos la cadena formateada. Estas funciones garantizarán el formato adecuado de los prompts para las solicitudes a la API de OpenAI y ofrecerán ayuda para la depuración a la hora de inspeccionar los datos de la API. Ahora podemos utilizarlas para crear la función básica que nos permita obtener las respuestas.

//+------------------------------------------------------------------+
//| Gets ChatGPT response via API                                    |
//+------------------------------------------------------------------+
string GetChatGPTResponse(string prompt) {                   //--- Get AI response
   string escapedPrompt = JsonEscape(prompt);                //--- Escape prompt
   string requestData = "{\"model\":\"" + OpenAI_Model + "\",\"messages\":[{\"role\":\"user\",\"content\":\"" + escapedPrompt + "\"}],\"max_tokens\":500}"; //--- Build JSON
   FileWriteString(logFileHandle, "Request Data: " + requestData + "\n"); //--- Log data
   char postData[];                                          //--- Post array
   int dataLen = StringToCharArray(requestData, postData, 0, WHOLE_ARRAY, CP_UTF8); //--- To char array
   ArrayResize(postData, dataLen - 1);                       //--- Remove terminator
   FileWriteString(logFileHandle, "Raw Post Data (Hex): " + LogCharArray(postData) + "\n"); //--- Log hex
   string headers = "Authorization: Bearer " + OpenAI_API_Key + "\r\n" + //--- Build headers
                    "Content-Type: application/json; charset=UTF-8\r\n" +
                    "Content-Length: " + IntegerToString(dataLen - 1) + "\r\n\r\n";
   FileWriteString(logFileHandle, "Request Headers: " + headers + "\n"); //--- Log headers
   char result[];                                            //--- Result array
   string resultHeaders;                                     //--- Result headers
   int res = WebRequest("POST", OpenAI_Endpoint, headers, 10000, postData, result, resultHeaders); //--- Send request
   if(res != 200) {                                          //--- Check status
      string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); //--- To string
      string errMsg = "API request failed: HTTP Code " + IntegerToString(res) + ", Error: " + IntegerToString(GetLastError()) + ", Response: " + response; //--- Error msg
      Print(errMsg);                                         //--- Print
      FileWriteString(logFileHandle, errMsg + "\n");          //--- Log
      FileWriteString(logFileHandle, "Raw Response Data (Hex): " + LogCharArray(result) + "\n"); //--- Log hex
      return errMsg;                                         //--- Return error
   }
   string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); //--- To string
   FileWriteString(logFileHandle, "API Response: " + response + "\n"); //--- Log response
   JsonValue jsonObject;                                     //--- JSON object
   int index = 0;                                            //--- Index
   char charArray[];                                         //--- Char array
   int arrayLength = StringToCharArray(response, charArray, 0, WHOLE_ARRAY, CP_UTF8); //--- To char
   if(!jsonObject.DeserializeFromArray(charArray, arrayLength, index)) { //--- Deserialize
      string errMsg = "Error: Failed to parse API response JSON: " + response; //--- Error
      Print(errMsg);                                         //--- Print
      FileWriteString(logFileHandle, errMsg + "\n");         //--- Log
      return errMsg;                                         //--- Return
   }
   JsonValue *error = jsonObject.FindChildByKey("error");    //--- Find error
   if(error != NULL) {                                       //--- If error
      string errMsg = "API Error: " + error["message"].ToString(); //--- Get message
      Print(errMsg);                                         //--- Print
      FileWriteString(logFileHandle, errMsg + "\n");         //--- Log
      return errMsg;                                         //--- Return
   }
   string content = jsonObject["choices"][0]["message"]["content"].ToString(); //--- Get content
   if(StringLen(content) > 0) {                              //--- If content
      StringReplace(content, "\\n", "\n");                   //--- Replace escapes
      StringTrimLeft(content);                               //--- Trim left
      StringTrimRight(content);                              //--- Trim right
      return content;                                        //--- Return
   }
   string errMsg = "Error: No content in API response: " + response; //--- Error
   Print(errMsg);                                            //--- Print
   FileWriteString(logFileHandle, errMsg + "\n");            //--- Log
   return errMsg;                                            //--- Return
}

Finalizamos la funcionalidad principal de nuestro programa implementando la función «GetChatGPTResponse» para gestionar la comunicación mediante la API con ChatGPT de OpenAI. En primer lugar, llamamos a «JsonEscape» para escapar el prompt de entrada, garantizando así la compatibilidad con JSON, y construimos una cadena JSON «requestData» con «OpenAI_Model», un array de mensajes de usuario que contiene el prompt escapado y «max_tokens» establecido en 500; a continuación, la registramos con FileWriteString en «logFileHandle». A continuación, convertimos «requestData» en un array de caracteres «postData» utilizando StringToCharArray con codificación UTF-8, ajustamos su tamaño con ArrayResize para eliminar el carácter de terminación nulo y registramos la representación hexadecimal mediante la función «LogCharArray».

Después creamos los encabezados HTTP con la «OpenAI_API_Key», el tipo de contenido y la longitud del contenido, los registramos y enviamos una solicitud POST a «OpenAI_Endpoint» utilizando WebRequest con un tiempo de espera de 10 segundos, almacenando la respuesta en «result». Si el código de respuesta no es 200, convertimos «result» en una cadena mediante CharArrayToString, registramos un error con «Print» y «FileWriteString», y devolvemos el mensaje de error. De lo contrario, analizamos la respuesta y la convertimos en un objeto «JsonValue» mediante «DeserializeFromArray» tras convertirla en un array de caracteres; si el análisis falla, registramos el error y devolvemos un mensaje de error. Comprobamos si existe una clave «error» mediante «FindChildByKey», registrando y devolviendo cualquier mensaje de error que haya.

Por último, extraemos la respuesta de la IA de «jsonObject['choices'][0]['message']['content']» utilizando «ToString», sustituimos los saltos de línea escapados con StringReplace, eliminamos los espacios en blanco al principio y al final con StringTrimLeft y StringTrimRight, y devolvemos el contenido si no está vacío; de lo contrario, registramos y devolvemos un mensaje de error. Esta función permitirá ahora al programa realizar consultas a ChatGPT y procesar sus respuestas para mostrarlas en pantalla. Lo que tenemos que hacer ahora es enviar nuestro primer mensaje. Tendremos que implementar la función de gestión de eventos «OnChartEvent» para detectar los cambios que realicemos y actuar en consecuencia.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Event handler
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_SubmitButton") { //--- Button click
      string prompt = (string)ObjectGetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT); //--- Get input
      if(StringLen(prompt) > 0) {                            //--- If not empty
         string response = GetChatGPTResponse(prompt);       //--- Get AI response
         Print("User: " + prompt);                           //--- Print user
         Print("AI: " + response);                           //--- Print AI
         conversationHistory += "You: " + prompt + "\nAI: " + response + "\n\n"; //--- Append history
         ObjectSetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT, ""); //--- Clear input
         UpdateResponseDisplay();                            //--- Update display
         FileWriteString(logFileHandle, "Prompt: " + prompt + " | Response: " + response + " | Time: " + TimeToString(TimeCurrent()) + "\n"); //--- Log
         ChartRedraw();                                      //--- Redraw
      }
   }
}

En este caso, mejoramos la interactividad del programa mediante la implementación de la función OnChartEvent para gestionar las interacciones del usuario en el gráfico. En primer lugar, comprobamos si el «id» del evento es CHARTEVENT_OBJECT_CLICK y si el «sparam» es «ChatGPT_SubmitButton», lo que indica que se ha pulsado el botón de enviar. Si es cierto, recuperamos la entrada del usuario del campo de texto «ChatGPT_InputEdit» mediante ObjectGetString con OBJPROP_TEXT y la almacenamos en la variable «prompt».

A continuación, si «prompt» no está vacío (lo comprobamos con StringLen), llamamos a «GetChatGPTResponse» para obtener la respuesta de la IA. Registramos el prompt del usuario y la respuesta de la IA, y los añadimos a «conversationHistory» con las etiquetas «You: » y «AI: », separados por saltos de línea. Vaciamos el campo de entrada utilizando ObjectSetString con una cadena vacía, actualizamos la visualización del gráfico llamando a «UpdateResponseDisplay», registramos la interacción con una marca de tiempo utilizando FileWriteString para escribir en «logFileHandle» y TimeToString para convertir la hora en una cadena, y actualizamos el gráfico. Esta implementación permite que el programa procese nuestros prompts y muestre las respuestas de la IA de forma interactiva, completando así la funcionalidad principal del panel interactivo. Tras la compilación, este es el resultado que obtenemos.

NUESTRO PRIMER PROMPT: HOLA, MUNDO

En la imagen podemos ver que hemos integrado con éxito el modelo de IA en nuestro programa. Intentemos abrir el archivo de registro y veamos si la interacción ha quedado registrada. Puede encontrar el archivo en la carpeta de archivos comunes, tal y como se muestra a continuación. Haga clic con el botón derecho del ratón y siga las instrucciones para abrirlo. Mire más abajo.

ARCHIVO DE REGISTRO

Al abrir el archivo, este es el resultado que obtenemos.

RESULTADO DEL ARCHIVO DE REGISTRO

En la imagen podemos ver que es posible registrar los datos de actividad, lo que nos permitirá hacer un seguimiento de lo que hacemos, de nuestros prompts y de las respuestas que obtenemos. Lo que tenemos que hacer ahora es asegurarnos de añadir un efecto al pasar el cursor por encima del botón «Enviar». Para ello, tendremos que realizar un seguimiento de las coordenadas del ratón mientras se mueve, lo que requerirá habilitar los eventos de movimiento del ratón en la capa superior del gráfico.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {                                               //--- Initialization
   button_darker_bg = DarkenColor(button_original_bg);       //--- Set darker button color
   logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT); //--- Open log
   if(logFileHandle == INVALID_HANDLE) {                     //--- Check handle
      Print("Failed to open log file: ", GetLastError());    //--- Print error
      return(INIT_FAILED);                                   //--- Fail init
   }
   FileSeek(logFileHandle, 0, SEEK_END);                     //--- Seek end
   FileWriteString(logFileHandle, "EA Initialized at " + TimeToString(TimeCurrent()) + "\n"); //--- Log init
   CreateDashboard();                                        //--- Create UI
   UpdateResponseDisplay();                                  //--- Update display
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);         //--- Enable mouse events
   return(INIT_SUCCEEDED);                                   //--- Success
}

Simplemente utilizamos la función ChartSetInteger para activar los eventos del ratón en el gráfico. Hemos resaltado la función para que quede más claro. A continuación, tenemos que pasar a la función OnChartEvent del gráfico y añadir la lógica que se ejecuta cuando se mueve el ratón.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Event handler
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_SubmitButton") { //--- Button click
      string prompt = (string)ObjectGetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT); //--- Get input
      if(StringLen(prompt) > 0) {                            //--- If not empty
         string response = GetChatGPTResponse(prompt);       //--- Get AI response
         Print("User: " + prompt);                           //--- Print user
         Print("AI: " + response);                           //--- Print AI
         conversationHistory += "You: " + prompt + "\nAI: " + response + "\n\n"; //--- Append history
         ObjectSetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT, ""); //--- Clear input
         UpdateResponseDisplay();                            //--- Update display
         FileWriteString(logFileHandle, "Prompt: " + prompt + " | Response: " + response + " | Time: " + TimeToString(TimeCurrent()) + "\n"); //--- Log
         ChartRedraw();                                      //--- Redraw
      }
   } else if(id == CHARTEVENT_MOUSE_MOVE) {                  //--- Mouse move
      int mouseX = (int)lparam;                              //--- X coord
      int mouseY = (int)dparam;                              //--- Y coord
      bool isOver = (mouseX >= 430 && mouseX <= 530 && mouseY >= 20 && mouseY <= 60); //--- Check hover
      if(isOver && !button_hover) {                          //--- Enter hover
         ObjectSetInteger(0, "ChatGPT_SubmitButton", OBJPROP_BGCOLOR, button_darker_bg); //--- Darken
         button_hover = true;                                //--- Set flag
         ChartRedraw();                                      //--- Redraw
      } else if(!isOver && button_hover) {                   //--- Exit hover
         ObjectSetInteger(0, "ChatGPT_SubmitButton", OBJPROP_BGCOLOR, button_original_bg); //--- Restore
         button_hover = false;                               //--- Clear flag
         ChartRedraw();                                      //--- Redraw
      }
   }
}

En la función OnChartEvent, completamos la implementación añadiendo efectos al pasar el cursor por encima de los elementos de la interfaz de usuario. Dentro de un bloque «else-if», comprobamos si el «id» del evento es CHARTEVENT_MOUSE_MOVE, lo que indica un movimiento del ratón, y convertimos «lparam» en «mouseX» y «dparam» en «mouseY» para obtener las coordenadas. A continuación, comprobamos si el cursor del ratón se encuentra sobre el botón de envío, verificando si «mouseX» está entre 430 y 530 y «mouseY» entre 20 y 60, y almacenamos el resultado en la variable «isOver».

Si «isOver» es verdadero y «button_hover» es falso (lo que indica que el ratón ha entrado en el área del botón), llamamos a ObjectSetInteger para establecer el color de fondo de «ChatGPT_SubmitButton» en «button_darker_bg» y crear así un efecto al pasar el ratón por encima, establecemos «button_hover» en verdadero y actualizamos el gráfico. Por el contrario, si «isOver» es falso y «button_hover» es verdadero (lo que indica que el ratón ha salido del área del botón), restablecemos el fondo del botón a «button_original_bg» mediante «ObjectSetInteger», establecemos «button_hover» en falso y llamamos a ChartRedraw para actualizar el gráfico. Ahora, al pasar el cursor por encima del botón, obtenemos el siguiente resultado.

EFECTO AL PASAR EL RATÓN POR ENCIMA DE UN BOTÓN

La imagen muestra que podemos añadir un efecto al pasar el cursor por encima del botón creado, con lo que logramos nuestro objetivo de crear un panel de control de IA interactivo. Lo que queda por hacer es probar el programa, y eso se trata en la siguiente sección.


Pruebas del programa ChatGPT

Hemos realizado las pruebas y, a continuación, se muestra la visualización completa en una única imagen de mapa de bits en formato Graphics Interchange Format (GIF).

GIF DE LA PRUEBA COMPLETA


Conclusión

En conclusión, hemos desarrollado en MQL5 un programa con ChatGPT integrado que aprovecha el framework de análisis de JSON de la parte anterior para enviar prompts a la API de OpenAI y visualizar las respuestas a través de un panel de control interactivo. Mediante una interfaz fácil de usar con un campo de entrada, un botón de envío y un área de visualización de respuestas, junto con la comunicación con la API y el registro de actividad, el programa facilita la obtención de información en tiempo real mediante inteligencia artificial. En las próximas secciones, actualizaremos la visualización para que admita más respuestas y permita el desplazamiento. No se lo pierda.

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

Archivos adjuntos |
a._ChatGPT_AI_EA.mq5 (116.72 KB)
Automatización del indicador del sentimiento del mercado Automatización del indicador del sentimiento del mercado
En este artículo, automatizaremos un indicador personalizado del sentimiento del mercado que clasifica las condiciones del mercado en alcistas, bajistas, de apetito por el riesgo, de aversión al riesgo y neutras. El asesor experto ofrece información en tiempo real sobre el sentimiento predominante, al tiempo que agiliza el proceso de análisis de las tendencias o la dirección actuales del mercado.
Modificación del algoritmo de optimización de Dingo — Dingo Optimization Algorithm M (DOAm) Modificación del algoritmo de optimización de Dingo — Dingo Optimization Algorithm M (DOAm)
La modificación del algoritmo de Dingo propuesta por el autor en este artículo ha elevado considerablemente el listón en la búsqueda del mejor de los mejores algoritmos de optimización. ¿Es posible obtener resultados aún mejores?
Redes neuronales en el trading: Señales de trading robustas en cualquier régimen de mercado (Conclusión) Redes neuronales en el trading: Señales de trading robustas en cualquier régimen de mercado (Conclusión)
El artículo ofrece un análisis detallado de la integración de los enfoques del framework ST-Expert en la arquitectura Extralonger, lo que permite analizar simultáneamente las representaciones vectoriales temporales y espaciales de los datos. Se presentan los resultados de las pruebas realizadas con datos históricos reales, que demuestran la eficacia del modelo y su robustez frente a las anomalías del mercado. El artículo describe la estructura modular del framework, que garantiza la reproducibilidad, la flexibilidad para la investigación y la capacidad de optimizar los componentes por etapas.
Desarrollo de estrategias de trading con los osciladores Parafrac y Parafrac V2: análisis del rendimiento de una única entrada Desarrollo de estrategias de trading con los osciladores Parafrac y Parafrac V2: análisis del rendimiento de una única entrada
Este artículo presenta el oscilador ParaFrac y su modelo V2 como herramientas de negociación. En él se describen tres estrategias de negociación desarrolladas a partir de estos indicadores. Cada estrategia se sometió a pruebas y se optimizó para identificar sus puntos fuertes y débiles. El análisis comparativo puso de manifiesto las diferencias de rendimiento entre el oscilador Parafrac original y su modelo V2.