Создание торговых систем с искусственным интеллектом на MQL5 (Часть 2): Разработка программы с интеграцией ChatGPT и пользовательским интерфейсом
Введение
В предыдущей статье (часть 1) был разработан модуль разбора JSON (JavaScript Object Notation) на языке MetaQuotes Language 5 (MQL5) на основе класса, обеспечивающего сериализацию и десериализацию JSON-данных для взаимодействия с API ИИ. Во второй части создается программа с интеграцией ChatGPT и пользовательским интерфейсом. Модуль JSON применяется для отправки промптов в API OpenAI и отображения ответов на графике MetaTrader 5, что позволяет интерактивно получать ИИ-аналитику для торговли. Мы рассмотрим следующие темы:
- Общая схема программы с интеграцией ChatGPT
- Настройка доступа к API OpenAI и конфигурация MetaTrader 5
- Реализация на MQL5
- Тестирование программы ChatGPT
- Заключение
К концу статьи будет создана рабочая программа MQL5 с интеграцией ChatGPT для интерактивных запросов к ИИ, готовая к расширению торговых стратегий. Приступим к реализации.
Общая схема программы с интеграцией ChatGPT
Разрабатываемая программа с ChatGPT интегрирует возможности модели ИИ в MQL5. Она позволяет отправлять промпты и получать аналитику непосредственно на графике MetaTrader 5, используя модуль разбора JSON из предыдущей части. Программа предоставит удобный интерфейс для ввода запросов и просмотра ответов ИИ. Это практический шаг к торговым системам на основе ИИ, способным анализировать рынки и предлагать стратегии.
Планируется создать панель управления с полем ввода, кнопкой отправки и областью отображения ответов. Эта панель позволит отправлять запросы ИИ и просматривать отформатированные ответы; все взаимодействия будут записываться в лог для отладки. Программа будет настроена для взаимодействия с API ИИ, обработки ответов и их четкого отображения. Это создаст основу для будущих программ, дополняющих систему автоматической торговлей на основе аналитики от ИИ. Ниже показано, чего мы хотим достичь.

Перейдем к настройке API и конфигурации MetaTrader 5.
Настройка доступа к API OpenAI и конфигурация MetaTrader 5
Чтобы программа с интеграцией ChatGPT могла взаимодействовать с API OpenAI, необходимо получить действующий API-ключ и настроить MetaTrader 5 (MT5) для разрешения HTTP-запросов для беспрепятственной интеграции. Рассмотрим получение API-ключа OpenAI, проверку его работоспособности с помощью тестов curl и настройку торгового терминала для разрешения URL-адреса эндпоинта API. Это обеспечит надежную связь.
Получение API-ключа OpenAI
Создайте аккаунт OpenAI: перейдите на platform.openai.com и зарегистрируйтесь либо войдите в аккаунт. Перейдите в раздел API и создайте новый API-ключ на панели API Keys. Безопасно скопируйте ключ, например начинающийся со строки "sk-", поскольку он будет использоваться в конфигурации программы.
Начальная страница:

Генерация секретного ключа:

Защитите API-ключ: храните ключ в безопасном месте, поскольку он предоставляет доступ к сервисам OpenAI. Обратите внимание, что при утечке ключ может быть аннулирован.
Проверка API-ключа с помощью тестов curl
Чтобы убедиться в работоспособности API-ключа, выполним тест curl из командной строки, моделирующий запрос к API:
- Установите curl: убедитесь, что curl установлен на вашей системе (доступен по умолчанию на большинстве систем Linux/macOS или может быть загружен для Windows).
- Выполните тестовый запрос: откройте терминал (в Windows – командную строку) и выполните следующую команду curl, заменив <YOUR_API_KEY> на действующий API-ключ 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}"- Проверьте ответ: успешный ответ (HTTP 200) возвращает JSON-объект с массивом choices, содержащим ответ ИИ (например, "content": "Это тестовый ответ"). Если в ответе присутствует поле error, например из-за недействительного API-ключа или превышения квоты, проверьте действительность ключа и платежный статус аккаунта OpenAI. Сохраните ответ в журнале, чтобы подтвердить работоспособность. После успешного запуска должен получиться результат, подобный следующему.

Если этот процесс вызывает затруднения, можно создать простой тестовый файл и запустить его в браузере следующим образом.
<!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>
Сохраните этот код в HTML-файл, например api_test.html, откройте его в браузере и добавьте свой ключ. Должен появиться следующий интерфейс.

При нажатии кнопки Test API должен появиться следующий ответ.

Настройка MetaTrader 5 для взаимодействия с API
Чтобы программа могла отправлять HTTP-запросы к эндпоинту OpenAI, торговый терминал настраивается следующим образом:
- Разрешение WebRequest. Откройте MT5, перейдите в Сервис → Настройки → Советники и установите флажок "Разрешить WebRequest для указанных URL".
- Добавление эндпоинта API. В поле "WebRequest URLs" добавьте https://api.openai.com, чтобы разрешить запросы к API OpenAI. Благодаря этому функция WebRequest программы сможет взаимодействовать без блокировки настройками безопасности MT5.
Ниже приведена полная визуализация.

Эта конфигурация позволит программе отправлять промпты и получать ответы ИИ, создавая основу для дальнейшей реализации. Перейдем к созданию программы.
Реализация на MQL5
Чтобы создать программу на языке MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку Indicators, нажмите кнопку "Создать" и следуйте указаниям для создания файла. После создания файла в среде разработки необходимо объявить глобальные переменные и входные параметры, используемые во всей программе.
//+------------------------------------------------------------------+ //| 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...";
Начнем реализацию программы с первичной настройки входных параметров для подключения к API OpenAI. Сначала определяется строковый входной параметр OpenAI_Model со значением "gpt-3.5-turbo". Он задает модель ChatGPT для API-запросов и при необходимости позволяет переключаться между моделями. Здесь можно выбрать другую модель. Затем присваиваем OpenAI_Endpoint значение https://api.openai.com/v1/chat/completions, определяющее URL-адрес для отправки HTTP POST-запросов к API OpenAI.
Далее настраивается целочисленный параметр MaxResponseLength со значением 500. Он ограничивает длину отображаемого ответа ChatGPT, обеспечивая удобочитаемый вывод на графике MetaTrader 5. Наконец, LogFileName присваивается значение "ChatGPT_EA_Log.txt" для логирования взаимодействий с API и ошибок. В OpenAI_API_Key жестко прописывается ранее проверенный API-ключ, например начинающийся с "sk-proj-", для аутентификации запросов и работы программы. Для логирования и визуализации кнопок добавляются следующие переменные.
//--- 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
К переменным добавлены комментарии, поясняющие их назначение. Далее определяются вспомогательные функции для создания интерфейса. Но сначала скопируйте логику JSON из предыдущей части, чтобы не создавать несколько файлов. Также можно создать подключаемый файл и включить его в программу, но это будет сделано по мере усложнения программы. Пока сохраним простую структуру программы.
//+------------------------------------------------------------------+ //| 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 }
Здесь создаются основные графические элементы для взаимодействия трейдера с программой на графике. Сначала реализуется функция createRecLabel. Она использует ObjectCreate для создания прямоугольной метки OBJ_RECTANGLE_LABEL в координатах xDistance, yDistance с размерами xSize, ySize. Через ObjectSetInteger задаются цвет фона OBJPROP_BGCOLOR, ширина и цвет рамки OBJPROP_COLOR, тип рамки OBJPROP_BORDER_TYPE со значением BORDER_FLAT, стиль OBJPROP_STYLE со значением STYLE_SOLID и угол привязки OBJPROP_CORNER со значением CORNER_LEFT_UPPER. Функция делает объект невыбираемым, отключает отображение объекта в качестве фонового, обновляет график через ChartRedraw и возвращает false при ошибке создания, фиксируя ошибку в журнале терминала через функцию Print.
Затем реализуется функция createButton, создающая кнопку OBJ_BUTTON с аналогичными параметрами координат и размеров. Через ObjectSetString и ObjectSetInteger задаются текст OBJPROP_TEXT, цвет текста OBJPROP_COLOR, размер и имя шрифта Arial Rounded MT Bold, цвет фона, цвет рамки и угол привязки. Опциональный флаг фона isBack определяет отображение на заднем плане. Функция задает для кнопки невыбираемое ненажатое состояние, обновляет график и возвращает false при ошибке, фиксируя ее в журнале.
Далее реализуется функция createEdit, создающая редактируемое текстовое поле OBJ_EDIT. Она принимает параметры текста, выравнивания OBJPROP_ALIGN со значением ALIGN_LEFT, режима только для чтения и аналогичных свойств оформления. Через ObjectSetString и ObjectSetInteger настраиваются внешний вид и поведение, после чего график обновляется, а ошибки обрабатываются аналогичным способом. Наконец, добавляется функция createLabel, создающая текстовую метку OBJ_LABEL с указанными текстом, цветом, размером и именем шрифта, углом и точкой привязки OBJPROP_ANCHOR со значением ANCHOR_LEFT_UPPER. Функция обеспечивает невыбираемое отображение метки и обновляет график. Эти функции формируют графическую основу панели управления программы, обеспечивая ввод запросов и отображение взаимодействий с ИИ. Требуется кнопка, которая затемняется при наведении. Для этого создается отдельная функция.
//+------------------------------------------------------------------+ //| 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 }
Здесь реализуется функция DarkenColor, принимающая значение цвета и необязательный коэффициент, по умолчанию 0,8, для уменьшения яркости. Красный, зеленый и синий компоненты извлекаются побитовыми операциями: "colorValue & 0xFF" для красного, "(colorValue >> 8) & 0xFF" для зеленого и "(colorValue >> 16) & 0xFF" для синего. Затем компоненты умножаются на коэффициент, объединяются с помощью побитовых сдвигов и операции ИЛИ "red \| (green << 8) \| (blue << 16)", после чего функция возвращает новый цвет. Теперь создадим панель управления.
//+------------------------------------------------------------------+ //| 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 }
Реализуется функция CreateDashboard, создающая пользовательский интерфейс на графике для взаимодействия с ИИ. Сначала вызывается createEdit для создания редактируемого текстового поля ChatGPT_InputEdit в координатах (20, 20) с размерами 400x40 пикселей. Для него задаются черный текст, шрифт Arial размером 12 пунктов, фон цвета WhiteSmoke, темно-серая рамка, выравнивание по левому краю и доступ для редактирования. Поле позволяет вводить промпты. Затем createButton добавляет ChatGPT_SubmitButton в координатах (430, 20) с размерами 100x40 пикселей. Кнопка имеет метку Send, белый текст, шрифт Arial размером 12 пунктов, фон королевского синего цвета button_original_bg, темно-синюю рамку и расположение в верхнем левом углу. Она запускает API-запросы.
Далее вызывается createRecLabel для создания фона ответов ChatGPT_ResponseBg в координатах (20, 70) с размерами 510x300 пикселей. Задаются белый фон, светло-серая рамка толщиной 2 пикселя, плоский тип рамки и сплошной стиль. Это создает четкую область для отображения ответов ИИ. Наконец, вызывается ChartRedraw для обновления графика, чтобы все элементы интерфейса стали видимыми. Теперь функцию можно вызвать в обработчике событий OnInit, чтобы увидеть полученный результат.
//+------------------------------------------------------------------+ //| 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 } }
В функции OnInit вызывается функция DarkenColor с аргументом button_original_bg для вычисления более темного оттенка для button_darker_bg. Это реализует эффект наведения для кнопки отправки. Затем с помощью FileOpen и флагов чтения, записи и текста открывается файл лога, указанный в LogFileName; его хэндл сохраняется в logFileHandle. Если хэндл недействителен, ошибка фиксируется в журнале терминала и возвращается INIT_FAILED. Это позволит отслеживать, что программа делает в фоновом режиме.
Далее с помощью FileSeek выполняется переход в конец файла лога, после чего через FileWriteString записывается сообщение инициализации с текущим временем, полученным с помощью функции TimeToString. Затем вызывается CreateDashboard для настройки пользовательского интерфейса, после чего возвращается INIT_SUCCEEDED, что означает успешную инициализацию. В функции OnDeinit выполняется очистка: ObjectsDeleteAll удаляет все графические объекты с префиксом "ChatGPT_", а при действительном logFileHandle файл лога закрывается с помощью FileClose. Пока программа прикреплена к графику, файл остается открытым. Это означает, что файл нельзя открыть, пока программа работает. Файл можно закрывать после отправки запроса и получения ответа, но это неудобно при активном взаимодействии. После запуска программы мы получаем следующий результат.

После создания интерфейса необходимо отобразить переписку как обычный чат. Однако MQL5 не предоставляет для этого прямого способа. Одна текстовая метка может отображать не более 63 символов. Поэтому реализуется механизм переноса текста, разбивающий переписку на несколько меток вместо одной и ограничивающий число символов в каждой метке заданным пределом. Ниже приведена используемая логика.
//+------------------------------------------------------------------+ //| 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 } } }
Реализуется функция WrapText для форматирования текста на графике и обеспечения читаемости ответов ИИ. Сначала определяется константа maxChars со значением 60 для ограничения длины строки, хотя предельное значение можно установить равным 63. Выходной массив wrappedLines очищается с помощью ArrayResize, а шрифт задается через TextSetFont с указанными именем и размером, масштабируемым как -fontSize * 10. Затем входной текст разбивается на абзацы с помощью StringSplit, где символ переноса строки служит разделителем; после этого перебирается каждый непустой абзац. Каждый абзац снова разбивается на слова с помощью StringSplit, но теперь с пробелом в качестве разделителя; затем инициализируется пустая переменная currentLine.
Далее слова перебираются в цикле: testLine формируется добавлением каждого слова с пробелом, если currentLine не пуста; ширина testLine измеряется через TextGetSize. Если ширина со смещением не превышает maxWidth, а длина меньше maxChars, обновляется currentLine. В противном случае непустая currentLine добавляется в wrappedLines, а новая строка начинается с текущего слова. Для слишком длинных слов перебираются символы, формируя wrappedWord и проверяя его ширину. Когда ширина превышает maxWidth или число символов превышает maxChars, wrappedWord добавляется в wrappedLines, а currentLine обновляется оставшейся частью слова. Наконец, оставшаяся currentLine добавляется в wrappedLines, обеспечивая форматирование всего текста для четкого отображения в области ответов программы. Теперь эта логика используется для обновления области отображения примером текста, поясняющим пользователю, как начать работу.
//+------------------------------------------------------------------+ //| 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 }
Здесь реализуется функция UpdateResponseDisplay для динамической отрисовки истории переписки. Сначала с помощью ObjectsTotal подсчитываются все графические объекты и выполняется обратный перебор. ObjectName получает имя каждого объекта, а ObjectDelete удаляет объекты с префиксами "ChatGPT_ResponseLine_" или "ChatGPT_MessageBg_", очищая предыдущие элементы отображения. Затем проверяется, пуста ли conversationHistory. Если да, вызывается createLabel для отображения сообщения-заполнителя в координатах (30, 80) серым цветом и шрифтом Arial размером 10 пунктов, предлагающего пользователю ввести сообщение; после перерисовки функция завершает работу.
Если conversationHistory содержит текст, задаются шрифт Arial, размер 10 и отступ 10. Затем maxWidth вычисляется как 510 минус удвоенный отступ, а WrapText разбивает текст на wrappedLines. Далее TextSetFont и TextGetSize определяют высоту строчки, maxVisibleLines вычисляется по высоте области ответов в 300 пикселей, а MathMax задает начальную строку для отображения последних строк. Массив wrappedLines перебирается начиная с startLine: для строк с префиксом "You:" currentColor задается серым, а для "AI:" – синим. Вызывается createLabel для отображения каждой строчки по координатам textX, textY; для каждой метки textY увеличивается на lineHeight, а имена создаются по шаблону "ChatGPT_ResponseLine_0". Наконец, вызывается ChartRedraw для обновления графика и четкого визуального отображения переписки. При вызове этой функции во время инициализации получается следующий результат.

На изображении видно, что в области отображения появилось начальное сообщение и она готова к отрисовке переписки. Теперь создадим функцию для получения ответа на отправленные промпты, но сначала определим необходимые вспомогательные функции.
//+------------------------------------------------------------------+ //| 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 }
Перед реализацией основной логики обработки ответов создаются вспомогательные функции для экранирования JSON-строк и отладки, обеспечивающие надежное взаимодействие с API. Сначала реализуется функция JsonEscape, принимающая строку и экранирующая специальные символы для соответствия JSON. StringReplace преобразует обратную косую черту в "\\", кавычку в "\"", символ переноса строки в "\n", возврат каретки в "\r", табуляцию в "\t" и перевод страницы в "\f". Затем StringGetCharacter перебирает каждый символ, проверяя управляющие символы с кодом ASCII менее 32 либо равным 127, и заменяет их Unicode-последовательностью "\uXXXX" через StringFormat. Строка обновляется с помощью StringSubstr путем объединения частей до и после экранированного символа; индекс корректируется для пропуска добавленных символов. Функция возвращает экранированную строку.
Затем разрабатывается функция LogCharArray, преобразующая массив символов в шестнадцатеричную строку для отладки. Она инициализирует пустую строку result, перебирает массив с помощью ArraySize, добавляет шестнадцатеричное значение каждого символа через StringFormat в формате "%02X" и возвращает отформатированную строку. Эти функции обеспечат правильное форматирование промптов для запросов к API OpenAI и поддержку отладки при проверке данных API. Теперь их можно использовать для создания основной функции получения ответов.
//+------------------------------------------------------------------+ //| 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 }
Основная функциональность программы завершается реализацией функции GetChatGPTResponse для взаимодействия с API ChatGPT от OpenAI. Сначала JsonEscape экранирует входной промпт, обеспечивая совместимость с JSON. Затем создается JSON-строка requestData, содержащая значение OpenAI_Model, массив сообщений пользователя с экранированным промптом и max_tokens со значением 500; эта строка записывается в logFileHandle с помощью FileWriteString. Затем requestData преобразуется в символьный массив postData с помощью StringToCharArray в кодировке UTF-8. ArrayResize изменяет его размер, удаляя завершающий нулевой символ; шестнадцатеричное представление фиксируется функцией LogCharArray.
Далее формируются HTTP-заголовки с OpenAI_API_Key, типом и длиной содержимого, после чего они записываются в лог. POST-запрос к OpenAI_Endpoint отправляется через WebRequest с таймаутом 10 секунд; ответ сохраняется в result. Если код ответа не равен 200, result преобразуется в строку с помощью CharArrayToString; ошибка фиксируется через Print и FileWriteString, после чего возвращается сообщение об ошибке. В противном случае ответ после преобразования в массив символов разбирается в объект JsonValue с помощью DeserializeFromArray. При ошибке разбора сообщение об ошибке фиксируется в логе и возвращается. С помощью FindChildByKey проверяется наличие ключа error; при его наличии сообщение об ошибке фиксируется в логе и возвращается.
Наконец, ответ ИИ извлекается из jsonObject['choices'][0]['message']['content'] с помощью ToString. Экранированные символы переноса строки заменяются через StringReplace, пробельные символы удаляются функциями StringTrimLeft и StringTrimRight. Если содержимое не пустое, оно возвращается; в противном случае ошибка фиксируется в журнале терминала и возвращается. Эта функция позволит программе отправлять запросы ChatGPT и обрабатывать ответы для отображения. Теперь необходимо отправить первое сообщение. Необходимо реализовать функцию обработчика событий OnChartEvent, чтобы отслеживать действия пользователя и реагировать на них.
//+------------------------------------------------------------------+ //| 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 } } }
Здесь повышается интерактивность программы за счет реализации функции OnChartEvent для обработки действий пользователя на графике. Сначала проверяется, равно ли id значению CHARTEVENT_OBJECT_CLICK и равно ли sparam значению "ChatGPT_SubmitButton", что указывает на нажатие кнопки отправки. Если условие выполняется, ввод пользователя извлекается из текстового поля ChatGPT_InputEdit с помощью ObjectGetString и OBJPROP_TEXT и сохраняется в переменную prompt.
Затем, если prompt не пустой, что проверяется с помощью StringLen, вызывается GetChatGPTResponse для получения ответа ИИ. Промпт пользователя и ответ ИИ записываются в лог и добавляются в conversationHistory с метками "You:" и "AI:", разделенными символами переноса строки. Поле ввода очищается через ObjectSetString пустой строкой; UpdateResponseDisplay обновляет отображение графика; взаимодействие с отметкой времени фиксируется через FileWriteString в logFileHandle и TimeToString; затем график обновляется. Эта реализация позволяет программе обрабатывать промпты и интерактивно отображать ответы ИИ, завершая основную функциональность панели управления. После компиляции получается следующий результат.

На изображении видно, что модель ИИ успешно интегрирована в программу. Откроем файл лога и проверим записанные данные. Файл находится в папке общих файлов, как показано ниже. Щелкните правой кнопкой мыши и следуйте указаниям, чтобы открыть файл. См. ниже.

При открытии файла получается следующий результат.

На изображении видно, что данные активности фиксируются в логе. Это позволяет отслеживать действия программы, промпты и получаемые ответы. Теперь необходимо добавить эффект наведения для кнопки отправки. Для этого необходимо отслеживать координаты движения мыши и включить события движения мыши на графике.
//+------------------------------------------------------------------+ //| 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 }
Для включения событий движения мыши на графике с помощью ChartSetInteger соответствующему свойству присваивается true. Для наглядности функция выделена. Затем мы переходим к функции OnChartEvent и добавляем логику обработки движения мыши.
//+------------------------------------------------------------------+ //| 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 } } }
В функции OnChartEvent реализация завершается добавлением эффектов наведения для пользовательского интерфейса. В блоке else if проверяется, равно ли id значению CHARTEVENT_MOUSE_MOVE, указывающему на движение мыши. Для получения координат lparam приводится к mouseX, а dparam – к mouseY. Затем проверяется, находится ли мышь над кнопкой отправки: mouseX должно быть между 430 и 530, а mouseY – между 20 и 60. Результат сохраняется в переменную isOver.
Если isOver равно true, а button_hover равно false, то есть мышь вошла в область кнопки, ObjectSetInteger задает цвет фона button_darker_bg для объекта ChatGPT_SubmitButton, чтобы создать эффект наведения. Затем button_hover присваивается true, а график обновляется. И наоборот, если isOver равно false, а button_hover равно true, то есть мышь покинула область кнопки, ObjectSetInteger восстанавливает фон кнопки button_original_bg. Затем button_hover принимает значение false, а ChartRedraw обновляет график. При наведении на кнопку получается следующий результат.

На изображении видно, что для созданной кнопки добавлен эффект наведения. Тем самым создана интерактивная панель управления с ИИ. Остается протестировать программу, и этому посвящен следующий раздел.
Тестирование программы с ChatGPT
Мы провели тестирование; ниже представлена итоговая визуализация в виде одного изображения формата Graphics Interchange Format (GIF).

Заключение
В итоге мы разработали программу с интеграцией ChatGPT на языке MQL5, использующую модуль разбора JSON из предыдущей части. Программа позволяет отправлять промпты в API OpenAI и просматривать ответы через интерактивную панель управления. Удобный интерфейс с полем ввода, кнопкой отправки и областью ответа, а также взаимодействие с API и журналирование позволяют получать аналитику от ИИ в реальном времени. В следующей статье мы обновим область отображения, чтобы она могла обрабатывать больше ответов и поддерживала прокрутку. Оставайтесь с нами.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19567
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Банк навыков)
Создание HTML-дашборда для Тестера стратегий и анализа проп-фирменных испытаний в MQL5
Изучение стандартной библиотеки MQL5 (Часть 12): Мультитаймфреймный дашборд на основе составного показателя
Изучение стандартной библиотеки MQL5 (Часть 11): Как создать матричный индикатор структуры рынка в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования