MQL5でのAI搭載取引システムの構築(第2回):ChatGPT統合型アプリケーションのUI開発
はじめに
前回の記事(第1回)では、MetaQuotes Language 5 (MQL5)でJSON (JavaScript Object Notation)解析フレームワークをクラスとして開発しました。このフレームワークにより、AI(人工知能)API(アプリケーションプログラミングインターフェース)とのデータやり取りに必要なJSONのシリアライズとデシリアライズが可能になりました。第2回では、このJSONフレームワークを活用して、ChatGPTを統合したユーザーインターフェース付きプログラムを作成します。本プログラムでは、OpenAIのAPIにプロンプトを送信し、その応答をMetaTrader 5のチャート上に表示することで、インタラクティブなAI駆動型の取引インサイトを提供します。本記事では以下のトピックを扱います。
この記事を通して、ChatGPTを統合したインタラクティブなMQL5プログラムを完成させ、取引戦略の強化に活用できるようになります。それでは、早速始めましょう。
ChatGPT AIプログラムフレームワークの理解
本記事で作成を目指すChatGPT AIプログラムは、AIモデルの機能をMQL5に統合することで、プロンプトを送信し、titlehttps://www.metatrader5.com/titleMetaTrader 5 のチャート上で直接AIからのインサイトを受け取ることを可能にします。これは前回の記事で構築したJSON解析の基盤を活用したものです。本プログラムは、クエリを入力してAIの応答を確認できるユーザーフレンドリーなインターフェースを提供し、市場の分析や戦略提案が可能なAI駆動型取引システムへの実用的な一歩となります。
プログラムの設計としては、入力フィールド、送信ボタン、応答表示エリアを備えたダッシュボードを作成し、AIにクエリを送信して整形された応答を確認できるようにします。また、すべてのやり取りをログに記録し、デバッグを容易にします。AI APIとの通信、応答の処理、表示を適切におこなうようプログラムを設定することで、将来的にはAIのインサイトに基づく自動取引機能を拡張する土台を整えます。以下は、目指すプログラムのイメージです。

APIとMetaTrader 5の設定に進みましょう。
OpenAI APIアクセスとMetaTrader 5設定の準備
ChatGPT AIプログラムがOpenAI APIと通信できるようにするためには、有効なAPIキーを取得し、MetaTrader 5 (MT5)でHTTPリクエストを許可する設定が必要です。本セッションでは、OpenAI APIキーの取得方法、curlによる動作確認、そしてMT5側でAPIエンドポイント(URL (Uniform Resource Locator))を許可するための設定手順について説明します。
OpenAI APIキーの取得
OpenAIアカウントの作成:platform.openai.comにアクセスし、アカウントを作成またはログインします。APIセクションにアクセスして、「API Keys」ダッシュボードから新しいAPIキーを生成します。生成されたキー(例:「sk-」で始まる文字列)はプログラム設定で使用するため、安全に保管してください。
初回ロード画面

秘密鍵の生成

APIキーの保護:APIキーはOpenAIサービスへのアクセス権限を持つため、厳重に管理してください。漏洩した場合はOpenAI側で無効化される可能性があります。
curlによるAPIキーの動作確認
APIキーが正しく機能しているか確認するため、curlを使用してAPIリクエストを実行します。
- Curlのインストール:curlがシステムにインストールされていることを確認します(Linux/macOSでは多くの環境に標準搭載されています。Windowsでは必要に応じてダウンロードします)。
- テストリクエストの実行:コマンドライン(Windowsではコマンドプロンプト)を開き、以下のコマンドを実行します。<YOUR_API_KEY>は実際のOpenAI APIキーに置き換えてください。
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が返ってきた場合は成功で、choices配列の中にAIの応答を含むJSONオブジェクトが返されます(例:“content”:“This is a test response”)。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)として保存し、Webブラウザで開いてキーを追加します。次のインターフェースが表示されます。

[Test API]をクリックすると、次のような応答が返されます。

MetaTrader 5でのAPI通信設定
OpenAIのエンドポイントへHTTPリクエストを送信するために、取引ターミナルで以下の設定をおこないます。
- Webリクエストを有効にする:MT5を開き、[ツール]>[オプション]>[エキスパートアドバイザ]に進み、[WebRequestを許可するURLリスト]を有効にします。
- APIエンドポイントを追加する:[WebRequestを許可するURLリスト]フィールドにhttps://api.openai.comを追加して、OpenAIの APIへのリクエストを許可します。これにより、WebRequest機能によるAPI通信がMT5のセキュリティ設定によってブロックされずに実行できます。
完全な視覚化は次のとおりです。

この設定により、プログラムはプロンプトを送信し、AI応答を受信できるようになります。次はプログラムの構築に進みます。
MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲータに移動して、インジケーターフォルダを見つけ、[新規]タブをクリックして、表示される手順に従ってファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用するグローバル変数と入力をいくつか宣言する必要があります。
//+------------------------------------------------------------------+ //| 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...";
プログラムの実装を開始するにあたり、まずOpenAI API への接続を構成するための入力パラメータを初期設定します。最初に、入力パラメータ「OpenAI_Model」を文字列として定義し、「gpt-3.5-turbo」を設定します。これにより、APIリクエストで使用するChatGPTモデルを指定でき、必要に応じてモデルを切り替える柔軟性を持たせています。ここを変更することで、任意のモデルを使用できます。次に、OpenAI_Endpointを「https://api.openai.com/v1/chat/completions」に設定し、OpenAI APIへHTTP POSTリクエストを送信するためのURLを定義します。
さらに、MaxResponseLengthを整数値500に設定し、MetaTrader 5のチャート上で表示されるChatGPTの応答長を制限することで、出力が扱いやすいようにします。最後に、ログファイル名を指定するため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ロジックを必ずコピー&ペーストしておく必要があります。プログラムがより複雑になった段階で、別ファイルを作成しインクルードする方法を取ることも可能ですが、現時点では処理をシンプルで分かりやすい形に保つため、すべて1つのファイルで進めていきます。
//+------------------------------------------------------------------+ //| 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を使用して指定した座標(xDistance, yDistance)とサイズ(xSize, ySize)に矩形ラベル(OBJ_RECTANGLE_LABEL)を描画します。この際、背景色(OBJPROP_BGCOLOR)、枠線の幅、色(OBJPROP_COLOR)、枠線タイプ(OBJPROP_BORDER_TYPEをBORDER_FLAT)、スタイル(OBJPROP_STYLEをSTYLE_SOLID)、コーナー位置(OBJPROP_CORNERをCORNER_LEFT_UPPER)などのプロパティをObjectSetIntegerで設定します。また、選択不可で非背景表示としたうえで、ChartRedrawでチャートを更新し、作成に失敗した場合はPrint関数でエラーを記録し、falseを返します。
次に、createButton関数を実装します。この関数では、同様の座標とサイズを指定してボタン(OBJ_BUTTON)を作成し、テキスト(OBJPROP_TEXT)、テキストカラー(OBJPROP_COLOR)、フォントサイズ、フォント名(Arial Rounded MT Bold)、背景色、枠線色、コーナー位置などをObjectSetStringおよびObjectSetIntegerで設定します。必要に応じて背景フラグ(isBack)を指定でき、選択不可かつ押下状態が残らないように設定します。チャート更新をおこない、作成に失敗した場合はエラーをログ出力してfalseを返します。
続いて、createEdit関数を実装します。この関数は編集可能なテキストフィールド(OBJ_EDIT)を作成し、テキスト、アライメント(OBJPROP_ALIGNをALIGN_LEFT)、読み取り専用設定、その他の外観プロパティをObjectSetStringとObjectSetIntegerで設定します。作成後にチャートを更新し、エラー発生時にはログを出力してfalseを返します。最後に、createLabel関数を追加します。この関数はテキストラベル(OBJ_LABEL)を作成し、テキスト、色、フォントサイズ、フォント名、コーナー位置、およびアンカー(OBJPROP_ANCHORをANCHOR_LEFT_UPPER)を設定します。選択不可として表示し、チャートを更新します。これらの関数により、本プログラムのダッシュボードに必要なグラフィカルな基盤が整い、AIとのやり取りの入力および表示が可能になります。また、ホバー時に暗くなるホバー可能ボタンも実装したいため、そのための関数も用意します。
//+------------------------------------------------------------------+ //| 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関数を実装し、AIとの対話がおこなえるようにします。まず、createEditを呼び出し、座標(20,20)、サイズ400×40ピクセルで編集可能なテキストフィールドChatGPT_InputEditを作成します。このフィールドは黒色テキスト、12ポイントのArialフォント、WhiteSmoke背景、ダークグレーの枠線、左寄せ、読み取り専用ではない設定とし、プロンプトを入力できるようにします。次に、createButtonを使用して、座標(430,20)、サイズ100×40ピクセルでChatGPT_SubmitButtonを追加します。テキストは「Send」、白色テキスト、12ポイントのArialフォント、背景色はロイヤルブルー(button_original_bg)、枠線はダークブルーとし、チャート左上付近に配置してAPIリクエストを送信するためのボタンとします。
続いて、createRecLabelを呼び出し、座標(20,70)、サイズ510×300ピクセルで応答背景「ChatGPT_ResponseBg」を作成します。背景色は白、枠線は2ピクセルのライトグレー、フラットな枠線タイプ、ソリッドスタイルとし、AIの応答を表示するための明確な領域を確保します。最後に、ChartRedrawを呼び出してチャートを更新し、すべてのUI要素が表示されるようにします。この関数をOnInitイベントハンドラから呼び出すことで、作成したUIを確認できます。
//+------------------------------------------------------------------+ //| 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となる暗めの色を計算します。これにより、送信ボタンにホバー時の暗転効果を適用できるようにします。続いて、LogFileNameで指定されたログファイルをFileOpenで開き、読み書きおよびテキストモードのフラグを指定してハンドルをlogFileHandleに保存します。ハンドルが無効であった場合、エラーログを出力し、INIT_FAILEDを返して初期化に失敗したことを示します。これにより、バックグラウンドで発生する処理の記録を確実におこなえるようになります。
次に、FileSeekを使用してログファイルの末尾へ移動し、FileWriteStringで初期化メッセージを書き込みます。この際、時刻情報はTimeToStringを利用して付加します。その後、CreateDashboardを呼び出してユーザーインターフェースを構築し、最後にINIT_SUCCEEDEDを返して初期化が成功したことを示します。OnDeinit関数では、ObjectsDeleteAllを呼び出し、「ChatGPT_」で始まるすべてのチャートオブジェクトを削除してクリーンアップをおこないます。また、logFileHandleが有効な場合はFileCloseでログファイルを閉じます。プログラムがチャートにアタッチされている間はログファイルを開いたまま保持するため、実行中にそのファイルを別途開くことはできません。送受信ごとにファイルを閉じる方式もありますが、アクティブにやり取りしている場合には都合が悪く、この方式がより実用的です。プログラムを実行すると、下のような結果が得られます。

インターフェースを作成できたので、次は会話内容を通常のチャットのように表示する方法を考える必要があります。しかし、MQL5にはこれを直接実現する手段がありません。ラベル関数には、1つのラベルにつき最大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関数を実装します。これはAIの応答を視認しやすく整形する目的で使用します。まず、1行あたりの長さ制限としてmaxCharsを60に設定します(63文字に上限を合わせることも可能です)。次に、出力配列wrappedLinesをArrayResizeでクリアし、指定されたフォントとフォントサイズ(-fontSize * 10でスケール)をTextSetFontで設定します。続いて、入力テキストを段落ごとに処理するため、改行文字を区切りにStringSplitで分割し、空ではない各段落について処理をおこないます。それぞれの段落に対し、スペースを区切りとして再度StringSplitを使って単語単位に分割し、currentLineを空の状態で初期化します。
その後、単語をループ処理し、currentLineが空でなければスペースを挟んで単語を追加したtestLineを構築し、TextGetSizeで表示幅を取得します。幅がmaxWidth以内で、かつ文字数がmaxChars以内である場合はcurrentLineを更新します。制限を超える場合は、currentLineが空でなければwrappedLinesに追加し、新たに現在の単語で行を開始します。単語自体が極端に長い場合は、文字単位でループ処理をおこない、wrappedWordとして1文字ずつ追加しながら幅を確認し、maxWidthまたはmaxCharsを超えた場合はwrappedLinesに追加し、残りの部分を新たな行として処理します。最後に、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で各オブジェクト名を取得します。そして、名前が「ChatGPT_ResponseLine_」または「ChatGPT_MessageBg_」で始まる場合はObjectDeleteを呼び出して削除し、以前の表示要素をクリアします。次に、conversationHistoryが空であるかを確認します。空の場合は、ユーザーにメッセージ入力を促すため、座標(30,80)にグレー、10ポイントArialフォントでプレースホルダーとなるメッセージをcreateLabelにより表示し、再描画後に処理を終了します。
conversationHistoryにテキストが含まれている場合は、フォントをArial、フォントサイズを10、paddingを10に設定し、応答領域の幅510ピクセルから左右のpadding×2を引いた値をmaxWidthとして計算します。その後、WrapTextを呼び出してテキストをwrappedLinesに分割します。続いて、TextSetFontとTextGetSizeで行の高さを取得し、300ピクセルの表示領域から計算してmaxVisibleLinesを算出します。さらに、MathMaxを使用して最新の行が表示されるように開始行を決定します。続いて、wrappedLinesのstartLineから順にループ処理し、行の内容に応じて色を分けます。具体的には、「You: 」で始まる行はグレー、「AI: 」で始まる行はブルーをcurrentColorとして設定し、createLabelを呼び出して各行を(textX,textY)に表示します。表示後はtextYに行の高さを加算し、ラベル名は「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 }
応答処理の本格的なロジックを実装する前に、API通信を堅牢におこなうためのJSON文字列エスケープ処理とデバッグ用ユーティリティ関数を作成します。まず、JsonEscape関数を実装します。この関数は文字列入力を受け取り、JSON仕様に準拠するよう特殊文字をエスケープします。具体的には、StringReplaceを使用し、バックスラッシュを「\\」、ダブルクォートを「\"」、改行を「\n」、キャリッジリターンを「\r」、タブを「\t」、フォームフィードを「\f」へと変換します。その後、StringGetCharacterで文字を1文字ずつ取得し、制御文字(ASCII値が32未満または127の場合)であれば、StringFormatを利用して「\uXXXX」形式のUnicodeエスケープシーケンスに置き換えます。この際、StringSubstrで置換前後の文字列を連結し、追加された文字数に応じてインデックスを調整します。最終的に、エスケープ済みの文字列を返します。
次に、デバッグ用としてLogCharArray関数を実装します。この関数はchar配列を16進文字列へ変換するためのものです。まず空の結果文字列を用意し、ArraySizeで配列要素数を取得してループ処理をおこないます。そして、各文字をStringFormatの「%02X」形式で16進数に変換し、結果文字列に追加します。最終的に、整形された16進文字列を返します。これらのユーティリティ関数により、OpenAI APIへのリクエストで送信するプロンプトが正しい形式で整えられ、さらに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 }
プログラムの中核となる機能を完成させるため、OpenAIのChatGPTと通信をおこなうGetChatGPTResponse関数を実装します。まず、JSON形式に適合させるためにJsonEscapeを呼び出して入力プロンプトをエスケープし、OpenAI_Model、エスケープ済みプロンプトを含むユーザーメッセージ配列、max_tokensを500としたJSON文字列requestDataを構築します。この内容はFileWriteStringを用いてlogFileHandleへログとして記録します。次に、requestDataをUTF-8としてStringToCharArrayでchar配列へ変換し、末尾に付加されるヌル終端を取り除くためにArrayResizeでサイズを調整します。その後、LogCharArray関数を使ってこの配列の16進表記をログへ出力します。
次に、OpenAI_API_Key、コンテンツタイプ、コンテンツの長さを含むHTTPヘッダーを作成してログに記録し、10秒のタイムアウトでWebRequestを使用してOpenAI_EndpointにPOSTリクエストを送信し、応答をresultに保存します。応答コードが200でない場合、resultをCharArrayToStringで文字列へ変換し、PrintとFileWriteStringでエラーをログに記録し、そのエラーメッセージを返します。それ以外の場合は、応答をchar配列へ変換したうえでDeserializeFromArrayを使用してJsonValueオブジェクトとして解析し、解析に失敗した場合はエラーをログに記録して返します。FindChildByKeyを使用して「error」キーが存在するかを確認し、存在する場合はそのエラーメッセージをログに記録して返します。
最後に、jsonObject["choices"][0]["message"]["content"]からToStringを使ってAIの返答を取得し、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であるかを確認し、送信ボタンがクリックされたことを検出します。これが真の場合、ObjectGetStringでOBJPROP_TEXTを取得し、テキスト入力フィールド「ChatGPT_InputEdit」からユーザー入力を取得して、prompt変数に保存します。
次に、そのpromptが空でないかをStringLenで確認し、空でなければGetChatGPTResponseを呼び出してAIの応答を取得します。そして、ユーザーのプロンプトとAIの応答をログに記録し、「You: 」と「AI: 」のラベルを付けて改行とともにconversationHistoryに追加します。続いて、ObjectSetStringで入力フィールドを空文字に設定してクリアし、UpdateResponseDisplayを呼び出してチャート上の表示を更新し、FileWriteStringとTimeToStringを使用してタイムスタンプ付きでログファイルに記録し、最後にチャートを再描画します。この実装により、プログラムはプロンプトを処理し、AIの応答をインタラクティブに表示できるようになり、ダッシュボードの中核機能が完成します。コンパイルすると、次のような結果が得られます。

画像から分かるように、AIモデルをプログラムへ正常に統合できています。次に、ログファイルを開いて内容を確認してみましょう。ログファイルは共通ファイルフォルダにあります。右クリックして指示に従うことで開くことができます。以下のとおりです。

ファイルを開くと、次のような結果が得られます。

画像から、アクティビティデータを記録できていることが確認できます。これにより、実行した操作、プロンプト、取得した応答を追跡できます。次におこなうべきことは、送信ボタンにホバー効果を追加することです。そのためには、マウス移動座標を追跡する必要があり、チャート上層でのマウスの動きを有効にする必要があります。
//+------------------------------------------------------------------+ //| 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関数を使用して、チャートのマウスイベントを有効にするだけで済みます。分かりやすくするために、その関数を強調してあります。次に、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を呼び出してChatGPT_SubmitButtonの背景色をbutton_darker_bgへ変更し、ホバー効果を適用します。そして、button_hoverをtrueに設定し、チャートを更新します。逆に、isOverがfalseで、かつbutton_hoverがtrue(マウスがボタン領域から離れた状態)である場合は、ObjectSetIntegerを使用してボタンの背景色をbutton_original_bgへ戻し、button_hoverをfalseに設定したうえで、ChartRedrawによりチャートを更新します。この状態でボタン上でマウスをホバーすると、次のような結果が得られます。

画像から分かるように、作成したボタンにホバー効果を適用できており、インタラクティブなAIダッシュボードの目標を達成しています。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
ChatGPTプログラムのテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

結論
これまでに作成してきたMQL5のChatGPT統合プログラムは、前回構築したJSON解析フレームワークを活用し、OpenAIのAPIへプロンプトを送信し、インタラクティブなダッシュボードを通して応答を表示できるものとなっています。入力フィールド、送信ボタン、応答表示エリアを備えた使いやすいインターフェースに加えて、API通信とログ記録を組み合わせることで、リアルタイムのAIによるインサイトを得られるようにしています。続くセクションでは、さらに多くの応答を扱えるように表示処理を更新し、スクロール可能にしていきます。どうぞご期待ください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19567
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
Market Sentimentインジケーターの自動化
ParafracおよびParafrac V2オシレーターを使用した取引戦略の開発:シングルエントリーパフォーマンスインサイト
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
ボラティリティベースのブレイクアウトシステムの開発
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索