English
preview
MQL5でのAI搭載取引システムの構築(第3回):複数行入力の克服、チャットの持続性の確保、シグナル生成

MQL5でのAI搭載取引システムの構築(第3回):複数行入力の克服、チャットの持続性の確保、シグナル生成

MetaTrader 5トレーディングシステム |
33 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第3回)では、MetaQuotes Language 5 (MQL5)で作成したChatGPT統合プログラムに、スクロール可能な単一チャット指向のUIを追加しました。これにより、タイムスタンプ、動的スクロール、マルチターン会話履歴が導入され、MetaTrader 5におけるAIとのインタラクションが大幅に向上しました。第4回では、改良されたテキストレンダリングを用いて複数行入力の制限を克服します。さらに、Advanced Encryption Standard (AES256)暗号化およびZIP圧縮を用いて保存された永続的なチャット履歴をナビゲートするためのサイドバーを追加します。また、AIによる市場分析を可能にするため、チャートデータを統合して初期の売買シグナルを生成します。本記事では以下のトピックを扱います。

  1. 複数行入力処理、サイドバーチャットの永続化、および売買シグナル生成の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

この記事を読み終える頃には、操作性が向上し、文脈を考慮した機能を備えたMQL5ベースのAI取引アシスタントを手にしているはずです。それでは始めましょう。


複数行入力処理、サイドバーチャットの永続化、および売買シグナル生成の理解

AI取引システムにおける複数行入力の処理は、複数行の市場説明やコードスニペットなど、詳細なプロンプトやデータを入力できるようにするために不可欠です。これにより、AIが複雑なクエリを切り捨てずに処理でき、単一行入力ではコンテキストが制限される可能性のある動的市場でも正確な応答が可能となります。チャットの永続化は、セッションをまたいで会話履歴を保存することで、同じ情報を繰り返すことなく、過去のAIの分析や洞察を基に作業を進められる点で価値があります。売買シグナル生成では、AIが市場データを分析し、実行可能な買いまたは売りの推奨を生成します。これにより、手動分析の負担を減らし、トレンド転換などのチャンスに迅速に対応可能となります。これらの機能を組み合わせることで、文脈を維持しつつAIをリアルタイム取引判断に統合した堅牢なシステムが構築され、ユーザー体験の向上やエラーの最小化、収益性の改善に繋がります。

本記事では、AIプログラムを拡張し、複数行入力を処理できる高度なテキスト処理を実装します。現状のロジックでは、最大63文字までしか入力できず、単純なプロンプトに制限されていました。これを改良することで、必要に応じて任意の行数を入力できるようにし、AIにより詳細な指示を与えて売買シグナルを生成させることが可能になります。また、チャットの永続化のために、安全な保存メカニズムも組み込みます。これにより、過去の会話を簡単に取得してナビゲートでき、同じ内容を何度も繰り返す必要がなくなります。チャットはAdvanced Encryption Standard (AES)で暗号化し、セキュリティを確保します。AESを選んだのは扱いやすさのためですが、必要に応じて他の方式を使用することも可能です。本記事では暗号化の詳細ロジックには踏み込みませんが、仕組みを示す図を以下に示します。

AES 256ワークフロー

ここでの考え方は、たとえばXAU/USDのチャートを分析する会話をおこなった後に、GBP/USDに関する別の会話を開始する、といった状況です。後になって過去の応答を確認したり、修正を加えたり、追加のプロンプトをおこないたい場合もあります。その際、会話全体を最初から繰り返すのではなく、保存された履歴を参照できるようにします。

これをより分かりやすくし、進化を確認できるようにするため、AIによる分析に基づいて初期の売買シグナルを生成するためのチャートデータ取得および統合機能を追加します。そのために、インターフェースを再定義し、アイコンやナビゲーションサイドバーを備えた、よりブランディングされたUIを設計します。直感的なナビゲーション要素を持つインターフェースを構築することで、チャット管理やシグナル表示を効率的におこなえるようにし、AIを取引戦略に活用したいユーザーにとって使いやすいシステムを実現します。以下は、最終的に実現するUIのイメージです。

改善版UIの計画


MQL5での実装

MQL5でアップグレードしたプログラムを実装するにあたり、まずコードのモジュール化をおこないます。これにより、現在積極的に使用しないファイルと使用するファイルを分離することが可能になります。以前にJSONファイルを分離すると述べていましたが、今回はそのタイミングです。また、ビットマップファイルを扱うための追加関数を定義し、それも別ファイルとして分離し、組み込みます。これにより、管理がより容易になります。

//+------------------------------------------------------------------+
//|                                         AI ChatGPT EA Part 4.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
#property icon "1. Forex Algo-Trader.ico"

#include "AI JSON FILE.mqh"                        //--- Include JSON parsing library
#include "AI CREATE OBJECTS FNS.mqh"               //--- Include object creation functions

ファイルはincludeとして作成し、#includeディレクティブを用いてプログラムに組み込みます。簡単にするため、プログラムがある基本フォルダに移動したため、ダブルクオートの形式を使用しています。もし別のフォルダにある場合は、ダブルクオートを山括弧(<)に置き換え、正しいパスを指定する必要があります。以下をご覧ください。

インクルードファイルディレクトリ

コードのセグメントを移動しただけです。ビットマップラベルを扱う関数が必要になるため、そのための関数を定義する必要があります。

//+------------------------------------------------------------------+
//| Creates a bitmap label object                                    |
//+------------------------------------------------------------------+
bool createBitmapLabel(string objName, int xDistance, int yDistance, int xSize, int ySize,
                       string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                          //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) { //--- Create bitmap label
      Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure
      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
   ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath);   //--- Set bitmap path
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);          //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);         //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);    //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);      //--- Disable selection
   return true;                                                //--- Return success
}

ビットマップラベルを作成する関数を実装します。これにより、UI上にスケーリングされたアイコンや画像を表示できるようになります(前述の説明でも確認できた通りです)。createBitmapLabel関数では、ObjectCreate関数を使用して、指定した座標(xDistance, yDistance)、サイズ(xSize、ySize)、ビットマップパス、色、コーナー位置(デフォルトはCORNER_LEFT_UPPER)でビットマップラベル(OBJ_BITMAP_LABEL)を生成します。さらに、画像のプロパティとしてOBJPROP_BMPFILEを設定し、選択不可かつ前面に表示されるようにObjectSetIntegerで設定します。作成に失敗した場合はPrintでログを出力します。以下に、このオブジェクト作成ファイルの全実装を示します。

//+------------------------------------------------------------------+
//|                                        AI CREATE OBJECTS FNS.mqh |
//|                           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"

//+------------------------------------------------------------------+
//| 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
}

//+------------------------------------------------------------------+
//| Creates a bitmap label object                                    |
//+------------------------------------------------------------------+
bool createBitmapLabel(string objName, int xDistance, int yDistance, int xSize, int ySize,
                       string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                          //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) { //--- Create bitmap label
      Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure
      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
   ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath);   //--- Set bitmap path
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);          //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);         //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);    //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);      //--- Disable selection
   return true;                                                //--- Return success
}

同じ実装をJSONにも適用しますが、整数や小数の文字列を変換できるように簡単なアップグレードを加えます。次におこなうべきことは、画像アイコンをビットマップファイルとして定義し、読み込む処理の改善です。サイズについては、後でリサイズするため心配する必要はありません。簡単にするため、画像は基本ディレクトリに置き、パスの管理に手間をかけないようにします。ビットマップファイルのみ扱えるため、フォーマットには注意してください。下記をご覧ください。

ビットマップファイル

ファイルが準備できたら、使用できるように組み込みます。ファイルはリソースとして作成し、最終プログラムで利用可能にするため、コンパイル後にユーザーが常にファイルを保持する必要はありません。以下のように実装します。

#resource "AI MQL5.bmp"
#define resourceImg "::AI MQL5.bmp"                //--- Define main image resource
#resource "AI LOGO.bmp"
#define resourceImgLogo "::AI LOGO.bmp"            //--- Define logo image resource
#resource "AI NEW CHAT.bmp"
#define resourceNewChat "::AI NEW CHAT.bmp"        //--- Define new chat icon resource
#resource "AI CLEAR.bmp"
#define resourceClear "::AI CLEAR.bmp"             //--- Define clear icon resource
#resource "AI HISTORY.bmp"
#define resourceHistory "::AI HISTORY.bmp"         //--- Define history icon resource

#resourceディレクティブを使用して、「AI MQL5.bmp」、「AI LOGO.bmp」、「AI NEW CHAT.bmp」、「AI CLEAR.bmp」、「AI HISTORY.bmp」の5つのビットマップファイルを組み込み、#defineディレクティブでそれぞれresourceImg、resourceImgLogo、resourceNewChat、resourceClear、resourceHistoryと定数化します。これにより、プログラム全体で一貫して参照できるようになります。これにより、メインダッシュボードのロゴ、サイドバーロゴ、アクションボタン用のカスタムアイコンを統合でき、インターフェースの見栄えと操作性が向上します。また、新しいダッシュボード要素を扱うために、追加の入力パラメータやグローバル変数を定義する必要があります。

#define P_SCROLL_LEADER "ChatGPT_P_Scroll_Leader"  //--- Define prompt scrollbar leader name
#define P_SCROLL_UP_REC "ChatGPT_P_Scroll_Up_Rec"  //--- Define prompt scroll up rectangle name
#define P_SCROLL_UP_LABEL "ChatGPT_P_Scroll_Up_Label" //--- Define prompt scroll up label name
#define P_SCROLL_DOWN_REC "ChatGPT_P_Scroll_Down_Rec" //--- Define prompt scroll down rectangle name
#define P_SCROLL_DOWN_LABEL "ChatGPT_P_Scroll_Down_Label" //--- Define prompt scroll down label name
#define P_SCROLL_SLIDER "ChatGPT_P_Scroll_Slider"  //--- Define prompt scrollbar slider name

input string OpenAI_Model = "gpt-4o";              // OpenAI model for API requests

input int MaxChartBars = 10;                       // Maximum recent bars to fetch details

string conversationHistory = "";                    //--- Store conversation history
string currentPrompt = "";                         //--- Store current user prompt
int logFileHandle = INVALID_HANDLE;                //--- Store log file handle
bool button_hover = false;                         //--- Track submit button hover state
color button_original_bg = clrRoyalBlue;           //--- Set submit button background color
color button_darker_bg;                            //--- Store submit button darker background
bool clear_hover = false;                          //--- Track clear button hover state
bool new_chat_hover = false;                       //--- Track new chat button hover state
color clear_original_bg = clrLightCoral;           //--- Set clear button background color
color clear_darker_bg;                             //--- Store clear button darker background
color new_chat_original_bg = clrLightBlue;         //--- Set new chat button background color
color new_chat_darker_bg;                          //--- Store new chat button darker background
color chart_button_bg = clrLightGreen;             //--- Set chart button background color
color chart_button_darker_bg;                      //--- Store chart button darker background
bool chart_hover = false;                          //--- Track chart button hover state
bool close_hover = false;                          //--- Track close button hover state
color close_original_bg = clrLightGray;            //--- Set close button background color
color close_darker_bg;                             //--- Store close button darker background
int g_sidebarWidth = 150;                         //--- Set sidebar width
int g_dashboardX = 10;                            //--- Set dashboard x position
int g_mainContentX = g_dashboardX + g_sidebarWidth; //--- Calculate main content x position
int g_mainY = 30;                                 //--- Set main content y position
int g_mainWidth = 550;                            //--- Set main content width
int g_dashboardWidth = g_sidebarWidth + g_mainWidth; //--- Calculate total dashboard width
int g_mainHeight = 0;                             //--- Store calculated main height
int g_padding = 10;                               //--- Set general padding
int g_sidePadding = 6;                            //--- Set side padding
int g_textPadding = 10;                           //--- Set text padding
int g_headerHeight = 40;                          //--- Set header height
int g_displayHeight = 280;                        //--- Set display height
int g_footerHeight = 180;                         //--- Set footer height
int g_promptHeight = 130;                         //--- Set prompt area height
int g_margin = 5;                                 //--- Set margin
int g_buttonHeight = 36;                          //--- Set button height
int g_editHeight = 25;                            //--- Set edit field height
int g_lineSpacing = 2;                            //--- Set line spacing
int g_editW = 0;                                  //--- Store edit field width
bool scroll_visible = false;                       //--- Track main scrollbar visibility
bool mouse_in_display = false;                    //--- Track mouse in main display area
int scroll_pos = 0;                               //--- Store main scroll position
int prev_scroll_pos = -1;                         //--- Store previous main scroll position
int slider_height = 20;                           //--- Set main slider height
bool movingStateSlider = false;                   //--- Track main slider drag state
int mlbDownX_Slider = 0;                          //--- Store main slider mouse x position
int mlbDownY_Slider = 0;                          //--- Store main slider mouse y position
int mlbDown_YD_Slider = 0;                        //--- Store main slider y distance
int g_total_height = 0;                           //--- Store total main display height
int g_visible_height = 0;                         //--- Store visible main display height
bool p_scroll_visible = false;                    //--- Track prompt scrollbar visibility
bool mouse_in_prompt = false;                     //--- Track mouse in prompt area
int p_scroll_pos = 0;                             //--- Store prompt scroll position
int p_slider_height = 20;                         //--- Set prompt slider height
bool p_movingStateSlider = false;                 //--- Track prompt slider drag state
int p_mlbDownX_Slider = 0;                        //--- Store prompt slider mouse x position
int p_mlbDownY_Slider = 0;                        //--- Store prompt slider mouse y position
int p_mlbDown_YD_Slider = 0;                      //--- Store prompt slider y distance
int p_total_height = 0;                           //--- Store total prompt height
int p_visible_height = 0;                         //--- Store visible prompt height
color g_promptBg = clrOldLace;                    //--- Set prompt background color
string g_scaled_image_resource = "";               //--- Store scaled main image resource
string g_scaled_sidebar_resource = "";            //--- Store scaled sidebar image resource
string g_scaled_newchat_resource = "";            //--- Store scaled new chat icon resource
string g_scaled_clear_resource = "";              //--- Store scaled clear icon resource
string g_scaled_history_resource = "";            //--- Store scaled history icon resource
bool dashboard_visible = true;                     //--- Track dashboard visibility
string dashboardObjects[20];                      //--- Store dashboard object names
int objCount = 0;                                 //--- Track number of dashboard objects

ここでは、まず新しいスクロールバー定義をインクルードします。その後、より複雑なデータを扱い、売買シグナルのような機密性の高いデータに対してより適切な応答を得るために、高度なAIモデル(gpt-4o)に変更します。ただし、使用するモデルは任意で選択可能です。また、新しいロジックを扱うために、追加のグローバル変数も定義しています。理解を助けるためにコメントも加えています。これで実装を開始できます。まず、画像をスケーリングするのに役立つヘルパー関数をいくつか定義します。

//+------------------------------------------------------------------+
//| Scale Image Using Bicubic Interpolation                          |
//+------------------------------------------------------------------+
void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) {
   uint scaled_pixels[];                          //--- Declare array for scaled pixels
   ArrayResize(scaled_pixels, new_width * new_height); //--- Resize scaled pixel array
   for (int y = 0; y < new_height; y++) {        //--- Iterate through new height
      for (int x = 0; x < new_width; x++) {      //--- Iterate through new width
         double original_x = (double)x * original_width / new_width; //--- Calculate original x coordinate
         double original_y = (double)y * original_height / new_height; //--- Calculate original y coordinate
         uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel color
         scaled_pixels[y * new_width + x] = pixel; //--- Store interpolated pixel
      }
   }
   ArrayResize(pixels, new_width * new_height);   //--- Resize original pixel array
   ArrayCopy(pixels, scaled_pixels);              //--- Copy scaled pixels to original array
}

//+------------------------------------------------------------------+
//| Perform Bicubic Interpolation for a Pixel                        |
//+------------------------------------------------------------------+
uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) {
   int x0 = (int)x;                               //--- Get integer x coordinate
   int y0 = (int)y;                               //--- Get integer y coordinate
   double fractional_x = x - x0;                  //--- Calculate fractional x
   double fractional_y = y - y0;                  //--- Calculate fractional y
   int x_indices[4], y_indices[4];                //--- Declare arrays for neighbor indices
   for (int i = -1; i <= 2; i++) {               //--- Iterate to set indices
      x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Clamp x indices
      y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Clamp y indices
   }
   uint neighborhood_pixels[16];                  //--- Declare array for 4x4 pixel neighborhood
   for (int j = 0; j < 4; j++) {                 //--- Iterate through y indices
      for (int i = 0; i < 4; i++) {              //--- Iterate through x indices
         neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store neighbor pixel
      }
   }
   uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare arrays for color components
   for (int i = 0; i < 16; i++) {                //--- Iterate through neighborhood pixels
      GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Extract ARGB components
   }
   uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha component
   uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red component
   uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green component
   uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue component
   return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine components into pixel color
}

//+------------------------------------------------------------------+
//| Perform Bicubic Interpolation for a Color Component              |
//+------------------------------------------------------------------+
double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) {
   double weights_x[4];                           //--- Declare x interpolation weights
   double t = fractional_x;                       //--- Set x fraction
   weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate first x weight
   weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate second x weight
   weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate third x weight
   weights_x[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate fourth x weight
   double y_values[4];                            //--- Declare y interpolation values
   for (int j = 0; j < 4; j++) {                 //--- Iterate through rows
      y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] +
                    weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Calculate row value
   }
   double weights_y[4];                           //--- Declare y interpolation weights
   t = fractional_y;                              //--- Set y fraction
   weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate first y weight
   weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate second y weight
   weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate third y weight
   weights_y[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate fourth y weight
   double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] +
                   weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Calculate final interpolated value
   return MathMax(0, MathMin(255, result));      //--- Clamp result to valid range
}

//+------------------------------------------------------------------+
//| Extract ARGB Components from a Pixel                             |
//+------------------------------------------------------------------+
void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) {
   alpha = (uchar)((pixel >> 24) & 0xFF);         //--- Extract alpha component
   red = (uchar)((pixel >> 16) & 0xFF);           //--- Extract red component
   green = (uchar)((pixel >> 8) & 0xFF);          //--- Extract green component
   blue = (uchar)(pixel & 0xFF);                  //--- Extract blue component
}

ここでは、チャット指向UIにおける高品質なビジュアルブランディングを確保するため、画像スケーリング関数を実装します。ScaleImage関数では、特定のUI要素に画像を合わせるために、まず新しいピクセル配列scaled_pixelsを作成し、比例マッピングによって元の座標を計算します。その後、BicubicInterpolateを適用して滑らかなピクセルカラーを生成し、ArrayCopy関数で結果を元の配列にコピーします。BicubicInterpolate関数では、4×4のピクセル近傍を使用し、GetArgbでARGBコンポーネントを分離します。その後、BicubicInterpolateComponentによる立方補間計算をおこない、各カラーチャンネルを補間します。これにより、サイドバーやダッシュボード上のアイコンやロゴが鮮明に表示されます。次に、応答表示スクロールバーのロジックと同様の形式で、プロンプト用スクロールバーの実装に取り組みます。

//+------------------------------------------------------------------+
//| Create Prompt Scrollbar Elements                                 |
//+------------------------------------------------------------------+
void CreatePromptScrollbar() {
   int promptX = g_mainContentX + g_sidePadding;  //--- Calculate prompt x position
   int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;              //--- Calculate prompt y position
   int promptW = g_mainWidth - 2 * g_sidePadding; //--- Calculate prompt width
   int scrollbar_x = promptX + promptW - 16;      //--- Calculate prompt scrollbar x position
   int scrollbar_y = promptY + 16;                //--- Set prompt scrollbar y position
   int scrollbar_width = 16;                      //--- Set prompt scrollbar width
   int scrollbar_height = g_promptHeight - 2 * 16; //--- Calculate prompt scrollbar height
   int button_size = 16;                          //--- Set prompt button size
   createRecLabel(P_SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'220,220,220', 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scrollbar leader rectangle
   createRecLabel(P_SCROLL_UP_REC, scrollbar_x, promptY, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scroll up button rectangle
   createLabel(P_SCROLL_UP_LABEL, scrollbar_x + 2, promptY + -2, CharToString(0x35), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER); //--- Create prompt scroll up arrow label
   createRecLabel(P_SCROLL_DOWN_REC, scrollbar_x, promptY + g_promptHeight - button_size, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scroll down button rectangle
   createLabel(P_SCROLL_DOWN_LABEL, scrollbar_x + 2, promptY + g_promptHeight - button_size + -2, CharToString(0x36), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER); //--- Create prompt scroll down arrow label
   p_slider_height = CalculatePromptSliderHeight(); //--- Calculate prompt slider height
   createRecLabel(P_SCROLL_SLIDER, scrollbar_x, promptY + g_promptHeight - button_size - p_slider_height, scrollbar_width, p_slider_height, clrSilver, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scrollbar slider rectangle
}

//+------------------------------------------------------------------+
//| Delete Prompt Scrollbar Elements                                 |
//+------------------------------------------------------------------+
void DeletePromptScrollbar() {
   ObjectDelete(0, P_SCROLL_LEADER);              //--- Delete prompt scrollbar leader
   ObjectDelete(0, P_SCROLL_UP_REC);              //--- Delete prompt scroll up rectangle
   ObjectDelete(0, P_SCROLL_UP_LABEL);            //--- Delete prompt scroll up label
   ObjectDelete(0, P_SCROLL_DOWN_REC);            //--- Delete prompt scroll down rectangle
   ObjectDelete(0, P_SCROLL_DOWN_LABEL);          //--- Delete prompt scroll down label
   ObjectDelete(0, P_SCROLL_SLIDER);              //--- Delete prompt scrollbar slider
}

//+------------------------------------------------------------------+
//| Calculate Prompt Scrollbar Slider Height                         |
//+------------------------------------------------------------------+
int CalculatePromptSliderHeight() {
   int scroll_area_height = g_promptHeight - 2 * 16; //--- Calculate prompt scroll area height
   int slider_min_height = 20;                    //--- Set minimum prompt slider height
   if (p_total_height <= p_visible_height) return scroll_area_height; //--- Return full height if no scroll needed
   double visible_ratio = (double)p_visible_height / p_total_height; //--- Calculate visible prompt height ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio); //--- Calculate proportional slider height
   return MathMax(slider_min_height, height);     //--- Return minimum or calculated height
}

//+------------------------------------------------------------------+
//| Update Prompt Scrollbar Slider Position                          |
//+------------------------------------------------------------------+
void UpdatePromptSliderPosition() {
   int promptX = g_mainContentX + g_sidePadding;  //--- Calculate prompt x position
   int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;              //--- Calculate prompt y position
   int scrollbar_x = promptX + (g_mainWidth - 2 * g_sidePadding) - 16; //--- Calculate prompt scrollbar x position
   int scrollbar_y = promptY + 16;                //--- Set prompt scrollbar y position
   int scroll_area_height = g_promptHeight - 2 * 16; //--- Calculate prompt scroll area height
   int max_scroll = MathMax(0, p_total_height - p_visible_height); //--- Calculate maximum prompt scroll distance
   if (max_scroll <= 0) return;                   //--- Exit if no scrolling needed
   double scroll_ratio = (double)p_scroll_pos / max_scroll; //--- Calculate prompt scroll position ratio
   int scroll_area_y_max = scrollbar_y + scroll_area_height - p_slider_height; //--- Calculate maximum prompt slider y position
   int scroll_area_y_min = scrollbar_y;           //--- Set minimum prompt slider y position
   int new_y = scroll_area_y_min + (int)(scroll_ratio * (scroll_area_y_max - scroll_area_y_min)); //--- Calculate new prompt slider y position
   new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); //--- Clamp y position to valid range
   ObjectSetInteger(0, P_SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Update prompt slider y position
}

//+------------------------------------------------------------------+
//| Update Prompt Scrollbar Button Colors                            |
//+------------------------------------------------------------------+
void UpdatePromptButtonColors() {
   int max_scroll = MathMax(0, p_total_height - p_visible_height); //--- Calculate maximum prompt scroll distance
   if (p_scroll_pos == 0) {                       //--- Check if at top of prompt display
      ObjectSetInteger(0, P_SCROLL_UP_LABEL, OBJPROP_COLOR, clrSilver); //--- Set prompt scroll up label to disabled color
   } else {                                       //--- Not at top
      ObjectSetInteger(0, P_SCROLL_UP_LABEL, OBJPROP_COLOR, clrDimGray); //--- Set prompt scroll up label to active color
   }
   if (p_scroll_pos == max_scroll) {              //--- Check if at bottom of prompt display
      ObjectSetInteger(0, P_SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrSilver); //--- Set prompt scroll down label to disabled color
   } else {                                       //--- Not at bottom
      ObjectSetInteger(0, P_SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrDimGray); //--- Set prompt scroll down label to active color
   }
}

//+------------------------------------------------------------------+
//| Scroll Up Prompt Display                                         |
//+------------------------------------------------------------------+
void PromptScrollUp() {
   if (p_scroll_pos > 0) {                        //--- Check if prompt scroll position allows scrolling up
      p_scroll_pos = MathMax(0, p_scroll_pos - 30); //--- Decrease prompt scroll position by 30
      UpdatePromptDisplay();                      //--- Update prompt display
      if (p_scroll_visible) {                     //--- Check if prompt scrollbar is visible
         UpdatePromptSliderPosition();            //--- Update prompt slider position
         UpdatePromptButtonColors();              //--- Update prompt scrollbar button colors
      }
   }
}

//+------------------------------------------------------------------+
//| Scroll Down Prompt Display                                       |
//+------------------------------------------------------------------+
void PromptScrollDown() {
   int max_scroll = MathMax(0, p_total_height - p_visible_height); //--- Calculate maximum prompt scroll distance
   if (p_scroll_pos < max_scroll) {               //--- Check if prompt scroll position allows scrolling down
      p_scroll_pos = MathMin(max_scroll, p_scroll_pos + 30); //--- Increase prompt scroll position by 30
      UpdatePromptDisplay();                      //--- Update prompt display
      if (p_scroll_visible) {                     //--- Check if prompt scrollbar is visible
         UpdatePromptSliderPosition();            //--- Update prompt slider position
         UpdatePromptButtonColors();              //--- Update prompt scrollbar button colors
      }
   }
}

複数行のユーザー入力を効果的に扱うため、スクロール可能なプロンプト領域を実装し、これまでの複雑なプロンプト表示の制限を解消します。CreatePromptScrollbar関数では、プロンプト領域用のスクロールバーを構築します。createRecLabelを使用してP_SCROLL_LEADER、P_SCROLL_UP_REC、P_SCROLL_DOWN_REC、P_SCROLL_SLIDERの矩形を描画し、createLabelでWebdingsの矢印を持つP_SCROLL_UP_LABELとP_SCROLL_DOWN_LABELを作成します。位置はg_mainContentX、g_sidePadding、g_promptHeightに基づいて計算します。

DeletePromptScrollbar関数では、ObjectDeleteを用いてこれらのオブジェクトを削除しクリーンアップします。CalculatePromptSliderHeight関数は、表示されるプロンプト領域の高さに応じてp_slider_heightをp_visible_heightとp_total_heightの比率で計算します。UpdatePromptSliderPosition関数は、p_scroll_posの比率に基づきP_SCROLL_SLIDERの位置をObjectSetIntegerで調整します。UpdatePromptButtonColors関数は、スクロール可能かどうかを示すためにP_SCROLL_UP_LABELとP_SCROLL_DOWN_LABELの色をclrSilverとclrDimGrayの間で切り替えます。最後に、PromptScrollUpとPromptScrollDownはp_scroll_posを30ピクセルずつ調整し、UpdatePromptDisplayを呼び出して、p_scroll_visibleがtrueの場合はスクロールバーの表示も更新します。これにより、インターフェース上で複数行入力のスムーズなナビゲーションが可能になります。

スクロールバーのロジックが整ったところで、次にプロンプトホルダーを作成します。この中に編集フィールドを配置します。編集フィールドでは依然として最大63文字の制限がありますが、セクションを連結することでこの長さ制限を回避できます。そのため、より大きなプレースホルダーが必要です。ここでの問題は、編集終了後に入力が行として追加される点です。段落が自然に続くように直感的に扱えるプログラムを実現するには、前の段落に追記する形にします。しかし、新しい段落を作成したい場合には別の方法が必要です。ユーザーに「\n」や「\newLine」を入力させる代わりに、ここでは特別な組み合わせとしてダブルピリオド「..」を使用し、この文字列が含まれている場合に新しい行として解釈することにしました。これは任意の組み合わせであり、必要に応じて自由に変更できます。このロジックを実装していきます。

//+------------------------------------------------------------------+
//| Split String on Delimiter                                        |
//+------------------------------------------------------------------+
int SplitOnString(string inputText, string delim, string &result[]) {
   ArrayResize(result, 0);                        //--- Clear result array
   int pos = 0;                                   //--- Initialize starting position
   int delim_len = StringLen(delim);              //--- Get delimiter length
   while (true) {                                 //--- Loop until string is fully processed
      int found = StringFind(inputText, delim, pos); //--- Find delimiter position
      if (found == -1) {                          //--- Check if no more delimiters
         string part = StringSubstr(inputText, pos); //--- Extract remaining string
         if (StringLen(part) > 0 || ArraySize(result) > 0) { //--- Check if part is non-empty or array not empty
            int size = ArraySize(result);         //--- Get current array size
            ArrayResize(result, size + 1);        //--- Resize result array
            result[size] = part;                  //--- Add remaining part
         }
         break;                                  //--- Exit loop
      }
      string part = StringSubstr(inputText, pos, found - pos); //--- Extract part before delimiter
      int size = ArraySize(result);              //--- Get current array size
      ArrayResize(result, size + 1);             //--- Resize result array
      result[size] = part;                       //--- Add part to array
      pos = found + delim_len;                   //--- Update position past delimiter
   }
   return ArraySize(result);                     //--- Return number of parts
}

//+------------------------------------------------------------------+
//| Replace Exact Double Periods with Newline                        |
//+------------------------------------------------------------------+
string ReplaceExactDoublePeriods(string text) {
   string result = "";                            //--- Initialize result string
   int len = StringLen(text);                     //--- Get text length
   for (int i = 0; i < len; i++) {               //--- Iterate through characters
      if (i + 1 < len && StringGetCharacter(text, i) == '.' && StringGetCharacter(text, i + 1) == '.') { //--- Check for double period
         bool preceded = (i > 0 && StringGetCharacter(text, i - 1) == '.'); //--- Check if preceded by period
         bool followed = (i + 2 < len && StringGetCharacter(text, i + 2) == '.'); //--- Check if followed by period
         if (!preceded && !followed) {            //--- Confirm exact double period
            result += "\n";                       //--- Append newline
            i++;                                  //--- Skip next period
         } else {                                 //--- Not exact double period
            result += ".";                        //--- Append period
         }
      } else {                                    //--- Non-double period character
         result += StringSubstr(text, i, 1);      //--- Append character
      }
   }
   return result;                                //--- Return processed string
}

//+------------------------------------------------------------------+
//| Create Prompt Placeholder Label                                  |
//+------------------------------------------------------------------+
void CreatePlaceholder() {
   if (ObjectFind(0, "ChatGPT_PromptPlaceholder") < 0 && StringLen(currentPrompt) == 0) { //--- Check if placeholder is needed
      int placeholderFontSize = 10;               //--- Set placeholder font size
      string placeholderFont = "Arial";            //--- Set placeholder font
      int lineHeight = TextGetHeight("A", placeholderFont, placeholderFontSize); //--- Calculate line height
      int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
      int promptY = footerY + g_margin;           //--- Calculate prompt y position
      int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
      int editX = g_mainContentX + g_sidePadding + g_textPadding; //--- Calculate edit field x position
      int labelY = editY + (g_editHeight - lineHeight) / 2; //--- Calculate label y position
      createLabel("ChatGPT_PromptPlaceholder", editX + 2, labelY, "Type your prompt here...", clrGray, placeholderFontSize, placeholderFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create placeholder label
      ChartRedraw();                              //--- Redraw chart to reflect changes
   }
}

//+------------------------------------------------------------------+
//| Delete Prompt Placeholder Label                                  |
//+------------------------------------------------------------------+
void DeletePlaceholder() {
   if (ObjectFind(0, "ChatGPT_PromptPlaceholder") >= 0) { //--- Check if placeholder exists
      ObjectDelete(0, "ChatGPT_PromptPlaceholder"); //--- Delete placeholder label
      ChartRedraw();                              //--- Redraw chart to reflect changes
   }
}

複数行入力処理を強化するために、まずSplitOnString関数を定義します。この関数は、指定した区切り文字を用いて入力テキストを配列に分割します。StringFindStringSubstrを使用してセグメントを抽出し、ArrayResizeで配列に格納することで、会話履歴の正確な解析を可能にします。ReplaceExactDoublePeriods関数では、ダブルピリオド「..」を改行に変換します。StringGetCharacterを使用して正確なダブルピリオドのみを判定することで、複数行のレンダリングを正確におこない、以前の表示制限を解消します。単一のピリオドや省略記号とは異なる扱いになるよう、特定の文字列を選択しています。

CreatePlaceholder関数では、currentPromptが空の場合に、プロンプト領域にChatGPT_PromptPlaceholderラベルをcreateLabelで追加します。TextGetHeightを使用して縦位置を調整します。一方、DeletePlaceholder関数は、テキストが入力された際にObjectDeleteでラベルを削除し、クリーンで直感的なプロンプト入力体験を提供します。常にコードをコンパイルして進捗を確認することは良いプログラミング習慣です。これにより、見落としを防ぐことができます。次に、ダッシュボードを作成し、関数を呼び出してメイン表示を更新し、プロンプトセクションを追加します。メインの背景ホルダーを拡張し、左サイドバーを収容できるようにします。

//+------------------------------------------------------------------+
//| Create Dashboard Elements                                        |
//+------------------------------------------------------------------+
void CreateDashboard() {
   objCount = 0;                             //--- Reset object count
   g_mainHeight = g_headerHeight + 2 * g_padding + g_displayHeight + g_footerHeight; //--- Calculate main dashboard height
   int displayX = g_mainContentX + g_sidePadding; //--- Calculate display x position
   int displayY = g_mainY + g_headerHeight + g_padding; //--- Calculate display y position
   int displayW = g_mainWidth - 2 * g_sidePadding; //--- Calculate display width
   int footerY = displayY + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;         //--- Calculate prompt y position
   int buttonsY = promptY + g_promptHeight + g_margin; //--- Calculate buttons y position
   int buttonW = 140;                        //--- Set button width
   int chartX = g_mainContentX + g_sidePadding; //--- Calculate chart button x position
   int sendX = g_mainContentX + g_mainWidth - g_sidePadding - buttonW; //--- Calculate send button x position
   dashboardObjects[objCount++] = "ChatGPT_MainContainer"; //--- Store main container object name
   createRecLabel("ChatGPT_MainContainer", g_mainContentX, g_mainY, g_mainWidth, g_mainHeight, clrWhite, 1, clrLightGray); //--- Create main container rectangle
   dashboardObjects[objCount++] = "ChatGPT_HeaderBg"; //--- Store header background object name
   createRecLabel("ChatGPT_HeaderBg", g_mainContentX, g_mainY, g_mainWidth, g_headerHeight, clrWhiteSmoke, 0, clrNONE); //--- Create header background rectangle
   string logo_resource = (StringLen(g_scaled_image_resource) > 0) ? g_scaled_image_resource : resourceImg; //--- Select header logo resource
   dashboardObjects[objCount++] = "ChatGPT_HeaderLogo"; //--- Store header logo object name
   createBitmapLabel("ChatGPT_HeaderLogo", g_mainContentX + g_sidePadding, g_mainY + (g_headerHeight - 40)/2, 104, 40, logo_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create header logo
   string title = "ChatGPT AI EA";           //--- Set dashboard title
   string titleFont = "Arial Rounded MT Bold"; //--- Set title font
   int titleSize = 14;                       //--- Set title font size
   TextSetFont(titleFont, titleSize);        //--- Set title font
   uint titleWid, titleHei;                  //--- Declare title dimensions
   TextGetSize(title, titleWid, titleHei);   //--- Get title dimensions
   int titleY = g_mainY + (g_headerHeight - (int)titleHei) / 2 - 4; //--- Calculate title y position
   int titleX = g_mainContentX + g_sidePadding + 104 + 5; //--- Calculate title x position
   dashboardObjects[objCount++] = "ChatGPT_TitleLabel"; //--- Store title label object name
   createLabel("ChatGPT_TitleLabel", titleX, titleY, title, clrDarkSlateGray, titleSize, titleFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create title label
   string dateStr = TimeToString(TimeTradeServer(), TIME_MINUTES); //--- Get current server time
   string dateFont = "Arial";                //--- Set date font
   int dateSize = 12;                        //--- Set date font size
   TextSetFont(dateFont, dateSize);          //--- Set date font
   uint dateWid, dateHei;                    //--- Declare date dimensions
   TextGetSize(dateStr, dateWid, dateHei);   //--- Get date dimensions
   int dateX = g_mainContentX + g_mainWidth / 2 - (int)(dateWid / 2) + 20; //--- Calculate date x position
   int dateY = g_mainY + (g_headerHeight - (int)dateHei) / 2 - 4; //--- Calculate date y position
   dashboardObjects[objCount++] = "ChatGPT_DateLabel"; //--- Store date label object name
   createLabel("ChatGPT_DateLabel", dateX, dateY, dateStr, clrSlateGray, dateSize, dateFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create date label
   int closeWidth = 100;                     //--- Set close button width
   int closeX = g_mainContentX + g_mainWidth - closeWidth - g_sidePadding; //--- Calculate close button x position
   int closeY = g_mainY + 4;                 //--- Calculate close button y position
   dashboardObjects[objCount++] = "ChatGPT_CloseButton"; //--- Store close button object name
   createButton("ChatGPT_CloseButton", closeX, closeY, closeWidth, g_headerHeight - 8, "Close", clrWhite, 11, close_original_bg, clrGray); //--- Create close button
   dashboardObjects[objCount++] = "ChatGPT_ResponseBg"; //--- Store response background object name
   createRecLabel("ChatGPT_ResponseBg", displayX, displayY, displayW, g_displayHeight, clrWhite, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID); //--- Create response background rectangle
   dashboardObjects[objCount++] = "ChatGPT_FooterBg"; //--- Store footer background object name
   createRecLabel("ChatGPT_FooterBg", g_mainContentX, footerY, g_mainWidth, g_footerHeight, clrGainsboro, 0, clrNONE); //--- Create footer background rectangle
   dashboardObjects[objCount++] = "ChatGPT_PromptBg"; //--- Store prompt background object name
   createRecLabel("ChatGPT_PromptBg", displayX, promptY, displayW, g_promptHeight, g_promptBg, 1, g_promptBg, BORDER_FLAT, STYLE_SOLID); //--- Create prompt background rectangle
   int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
   int editX = displayX + g_textPadding;     //--- Calculate edit field x position
   g_editW = displayW - 2 * g_textPadding;   //--- Calculate edit field width
   dashboardObjects[objCount++] = "ChatGPT_PromptEdit"; //--- Store prompt edit object name
   createEdit("ChatGPT_PromptEdit", editX, editY, g_editW, g_editHeight, "", clrBlack, 13, DarkenColor(g_promptBg,0.93), DarkenColor(g_promptBg,0.87),"Calibri"); //--- Create prompt edit field
   ObjectSetInteger(0, "ChatGPT_PromptEdit", OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set edit field border type
   dashboardObjects[objCount++] = "ChatGPT_GetChartButton"; //--- Store chart button object name
   createButton("ChatGPT_GetChartButton", chartX, buttonsY, buttonW, g_buttonHeight, "Get Chart Data", clrWhite, 11, chart_button_bg, clrDarkGreen); //--- Create chart data button
   dashboardObjects[objCount++] = "ChatGPT_SendPromptButton"; //--- Store send button object name
   createButton("ChatGPT_SendPromptButton", sendX, buttonsY, buttonW, g_buttonHeight, "Send Prompt", clrWhite, 11, button_original_bg, clrDarkBlue); //--- Create send prompt button
   ChartRedraw();                            //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Expert Initialization Function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   button_darker_bg = DarkenColor(button_original_bg); //--- Set darker background for submit button
   clear_darker_bg = DarkenColor(clear_original_bg);  //--- Set darker background for clear button
   new_chat_darker_bg = DarkenColor(new_chat_original_bg); //--- Set darker background for new chat button
   chart_button_darker_bg = DarkenColor(chart_button_bg); //--- Set darker background for chart button
   close_darker_bg = DarkenColor(close_original_bg);  //--- Set darker background for close button
   logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT); //--- Open log file for reading and writing
   if (logFileHandle == INVALID_HANDLE) {             //--- Check if file opening failed
      Print("Failed to open log file: ", GetLastError()); //--- Log error
      return(INIT_FAILED);                            //--- Return initialization failure
   }
   FileSeek(logFileHandle, 0, SEEK_END);             //--- Move file pointer to end
   uint img_pixels[];                                //--- Declare array for main image pixels
   uint orig_width = 0, orig_height = 0;             //--- Initialize main image dimensions
   bool image_loaded = ResourceReadImage(resourceImg, img_pixels, orig_width, orig_height); //--- Load main image resource
   if (image_loaded && orig_width > 0 && orig_height > 0) { //--- Check if main image loaded successfully
      ScaleImage(img_pixels, (int)orig_width, (int)orig_height, 104, 40); //--- Scale main image to 104x40
      g_scaled_image_resource = "::ChatGPT_HeaderImageScaled"; //--- Set scaled main image resource name
      if (ResourceCreate(g_scaled_image_resource, img_pixels, 104, 40, 0, 0, 104, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled main image resource
         Print("Scaled image resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled image resource"); //--- Log error
      }
   } else {                                          //--- Handle main image load failure
      Print("Failed to load original image resource"); //--- Log error
   }
   uint img_pixels_logo[];                           //--- Declare array for logo image pixels
   uint orig_width_logo = 0, orig_height_logo = 0;   //--- Initialize logo image dimensions
   bool image_loaded_logo = ResourceReadImage(resourceImgLogo, img_pixels_logo, orig_width_logo, orig_height_logo); //--- Load logo image resource
   if (image_loaded_logo && orig_width_logo > 0 && orig_height_logo > 0) { //--- Check if logo image loaded successfully
      ScaleImage(img_pixels_logo, (int)orig_width_logo, (int)orig_height_logo, 81, 81); //--- Scale logo image to 81x81
      g_scaled_sidebar_resource = "::ChatGPT_SidebarImageScaled"; //--- Set scaled logo image resource name
      if (ResourceCreate(g_scaled_sidebar_resource, img_pixels_logo, 81, 81, 0, 0, 81, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled logo image resource
         Print("Scaled sidebar image resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled sidebar image resource"); //--- Log error
      }
   } else {                                          //--- Handle logo image load failure
      Print("Failed to load sidebar image resource"); //--- Log error
   }
   uint img_pixels_newchat[];                        //--- Declare array for new chat icon pixels
   uint orig_width_newchat = 0, orig_height_newchat = 0; //--- Initialize new chat icon dimensions
   bool image_loaded_newchat = ResourceReadImage(resourceNewChat, img_pixels_newchat, orig_width_newchat, orig_height_newchat); //--- Load new chat icon resource
   if (image_loaded_newchat && orig_width_newchat > 0 && orig_height_newchat > 0) { //--- Check if new chat icon loaded successfully
      ScaleImage(img_pixels_newchat, (int)orig_width_newchat, (int)orig_height_newchat, 30, 30); //--- Scale new chat icon to 30x30
      g_scaled_newchat_resource = "::ChatGPT_NewChatIconScaled"; //--- Set scaled new chat icon resource name
      if (ResourceCreate(g_scaled_newchat_resource, img_pixels_newchat, 30, 30, 0, 0, 30, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled new chat icon resource
         Print("Scaled new chat icon resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled new chat icon resource"); //--- Log error
      }
   } else {                                          //--- Handle new chat icon load failure
      Print("Failed to load new chat icon resource"); //--- Log error
   }
   uint img_pixels_clear[];                          //--- Declare array for clear icon pixels
   uint orig_width_clear = 0, orig_height_clear = 0; //--- Initialize clear icon dimensions
   bool image_loaded_clear = ResourceReadImage(resourceClear, img_pixels_clear, orig_width_clear, orig_height_clear); //--- Load clear icon resource
   if (image_loaded_clear && orig_width_clear > 0 && orig_height_clear > 0) { //--- Check if clear icon loaded successfully
      ScaleImage(img_pixels_clear, (int)orig_width_clear, (int)orig_height_clear, 30, 30); //--- Scale clear icon to 30x30
      g_scaled_clear_resource = "::ChatGPT_ClearIconScaled"; //--- Set scaled clear icon resource name
      if (ResourceCreate(g_scaled_clear_resource, img_pixels_clear, 30, 30, 0, 0, 30, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled clear icon resource
         Print("Scaled clear icon resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled clear icon resource"); //--- Log error
      }
   } else {                                          //--- Handle clear icon load failure
      Print("Failed to load clear icon resource"); //--- Log error
   }
   uint img_pixels_history[];                        //--- Declare array for history icon pixels
   uint orig_width_history = 0, orig_height_history = 0; //--- Initialize history icon dimensions
   bool image_loaded_history = ResourceReadImage(resourceHistory, img_pixels_history, orig_width_history, orig_height_history); //--- Load history icon resource
   if (image_loaded_history && orig_width_history > 0 && orig_height_history > 0) { //--- Check if history icon loaded successfully
      ScaleImage(img_pixels_history, (int)orig_width_history, (int)orig_height_history, 30, 30); //--- Scale history icon to 30x30
      g_scaled_history_resource = "::ChatGPT_HistoryIconScaled"; //--- Set scaled history icon resource name
      if (ResourceCreate(g_scaled_history_resource, img_pixels_history, 30, 30, 0, 0, 30, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled history icon resource
         Print("Scaled history icon resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled history icon resource"); //--- Log error
      }
   } else {                                          //--- Handle history icon load failure
      Print("Failed to load history icon resource"); //--- Log error
   }
   g_mainHeight = g_headerHeight + 2 * g_padding + g_displayHeight + g_footerHeight; //--- Calculate main dashboard height
   createRecLabel("ChatGPT_DashboardBg", g_dashboardX, g_mainY, g_dashboardWidth, g_mainHeight, clrWhite, 1, clrLightGray); //--- Create dashboard background rectangle
   ObjectSetInteger(0, "ChatGPT_DashboardBg", OBJPROP_ZORDER, 0); //--- Set dashboard background z-order
   createRecLabel("ChatGPT_SidebarBg", g_dashboardX+2, g_mainY+2, g_sidebarWidth - 2 - 1, g_mainHeight - 2 - 2, clrGainsboro, 1, clrNONE); //--- Create sidebar background rectangle
   ObjectSetInteger(0, "ChatGPT_SidebarBg", OBJPROP_ZORDER, 0); //--- Set sidebar background z-order
   CreateDashboard();                                //--- Create dashboard elements
   UpdateResponseDisplay();                          //--- Update response display
   CreatePlaceholder();                              //--- Create prompt placeholder
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enable mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); //--- Enable mouse wheel events
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);     //--- Enable chart mouse scrolling
   return(INIT_SUCCEEDED);                           //--- Return initialization success
}

まず、CreateDashboard関数を拡張します。この関数では、g_mainContentX、g_sidePadding、g_headerHeight、g_displayHeight、g_footerHeightを使用してレイアウトの寸法を計算し、メインインターフェースを構築します。ChatGPT_MainContainerなどのオブジェクトを作成し、幅を拡張します。また、createRecLabelでChatGPT_HeaderBgやChatGPT_FooterBgを作成し、createBitmapLabelでスケーリング済みヘッダロゴChatGPT_HeaderLogoをg_scaled_image_resourceまたはresourceImgを使って表示します。さらに、タイトルChatGPT_TitleLabelとタイムスタンプChatGPT_DateLabelをcreateLabelで作成し、ブランド表示と文脈を明確にします。加えて、createEditでChatGPT_PromptEditフィールドを追加し、市場データ統合用のChatGPT_GetChartButton、プロンプト送信用のChatGPT_SendPromptButton、ダッシュボード非表示用のChatGPT_CloseButtonを作成します。作成したオブジェクト名はdashboardObjectsに格納し、管理を容易にします。

OnInitイベントハンドラでは、DarkenColorを用いてボタン色を暗めに設定し、FileOpenでログファイルChatGPT_EA_Log.txtを開きます。さらに、ScaleImageとResourceCreateを使用してビットマップリソース(AI MQL5.bmp、AI LOGO.bmp、AI NEW CHAT.bmp、AI CLEAR.bmp、AI HISTORY.bmp)をスケーリングし、表示の一貫性を確保します。その後、CreateDashboard、UpdateResponseDisplay、CreatePlaceholderを呼び出してダッシュボードをセットアップし、ChartSetIntegerでマウスイベントを有効化して将来的なインタラクティブ操作に備えます。コンパイルすると、次の結果が得られます。

プロンプトホルダー付きのアップデート版表示

表示が更新されたところで、次はチャートデータの取得、プロンプト表示への反映、および分析送信の処理に取り組みます。その際、重要なデータを扱うためUTF-8の取り扱いを改善し、またログ機能を強化します。ログは後で削除可能ですが、現在の処理状況を正確に把握し、問題発生時に迅速に対応できるようにするためです。まず、プロンプト表示を更新する関数から開始します。この関数は、応答表示の更新と同様のアプローチで実装します。

//+------------------------------------------------------------------+
//| Update Prompt Display                                            |
//+------------------------------------------------------------------+
void UpdatePromptDisplay() {
   int total = ObjectsTotal(0, 0, -1);       //--- Get total number of chart objects
   for (int j = total - 1; j >= 0; j--) {   //--- Iterate through objects in reverse
      string name = ObjectName(0, j, 0, -1); //--- Get object name
      if (StringFind(name, "ChatGPT_PromptLine_") == 0) { //--- Check if object is prompt line
         ObjectDelete(0, name);              //--- Delete prompt line object
      }
   }
   int promptX = g_mainContentX + g_sidePadding; //--- Calculate prompt x position
   int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;         //--- Calculate prompt y position
   int textX = promptX + g_textPadding;      //--- Calculate text x position
   int textY = promptY + g_textPadding;      //--- Calculate text y position
   int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
   int fullMaxWidth = g_mainWidth - 2 * g_sidePadding - 2 * g_textPadding; //--- Calculate maximum text width
   int visibleHeight = editY - textY - g_textPadding - g_margin; //--- Calculate visible height
   if (currentPrompt == "") {                //--- Check if prompt is empty
      p_total_height = 0;                    //--- Set total prompt height to zero
      p_visible_height = visibleHeight;       //--- Set visible prompt height
      if (p_scroll_visible) {                //--- Check if prompt scrollbar is visible
         DeletePromptScrollbar();            //--- Delete prompt scrollbar
         p_scroll_visible = false;           //--- Set prompt scrollbar visibility to false
      }
      ObjectSetInteger(0, "ChatGPT_PromptEdit", OBJPROP_XSIZE, g_editW); //--- Set edit field width
      ChartRedraw();                         //--- Redraw chart
      return;                                //--- Exit function
   }
   string font = "Arial";                    //--- Set font for prompt
   int fontSize = 10;                        //--- Set font size for prompt
   int lineHeight = TextGetHeight("A", font, fontSize); //--- Calculate line height
   int adjustedLineHeight = lineHeight + g_lineSpacing; //--- Adjust line height with spacing
   p_visible_height = visibleHeight;         //--- Set global visible prompt height
   string wrappedLines[];                    //--- Declare array for wrapped lines
   WrapText(currentPrompt, font, fontSize, fullMaxWidth, wrappedLines); //--- Wrap prompt text
   int totalLines = ArraySize(wrappedLines); //--- Get number of wrapped lines
   int totalHeight = totalLines * adjustedLineHeight; //--- Calculate total height
   bool need_scroll = totalHeight > visibleHeight; //--- Check if scrollbar is needed
   bool should_show_scrollbar = false;       //--- Initialize scrollbar visibility
   int reserved_width = 0;                   //--- Initialize reserved width for scrollbar
   if (ScrollbarMode != SCROLL_WHEEL_ONLY) { //--- Check if scrollbar mode allows display
      should_show_scrollbar = need_scroll && (ScrollbarMode == SCROLL_DYNAMIC_ALWAYS || (ScrollbarMode == SCROLL_DYNAMIC_HOVER && mouse_in_prompt)); //--- Determine if scrollbar should show
      if (should_show_scrollbar) {           //--- Check if scrollbar is visible
         reserved_width = 16;                //--- Reserve width for scrollbar
      }
   }
   if (reserved_width > 0) {                 //--- Check if scrollbar space reserved
      WrapText(currentPrompt, font, fontSize, fullMaxWidth - reserved_width, wrappedLines); //--- Re-wrap text with adjusted width
      totalLines = ArraySize(wrappedLines);  //--- Update number of wrapped lines
      totalHeight = totalLines * adjustedLineHeight; //--- Update total height
   }
   p_total_height = totalHeight;             //--- Set global total prompt height
   bool prev_p_scroll_visible = p_scroll_visible; //--- Store previous prompt scrollbar visibility
   p_scroll_visible = should_show_scrollbar; //--- Update prompt scrollbar visibility
   if (p_scroll_visible != prev_p_scroll_visible) { //--- Check if visibility changed
      if (p_scroll_visible) {                //--- Check if scrollbar should be shown
         CreatePromptScrollbar();            //--- Create prompt scrollbar
      } else {                               //--- Scrollbar not needed
         DeletePromptScrollbar();            //--- Delete prompt scrollbar
      }
   }
   ObjectSetInteger(0, "ChatGPT_PromptEdit", OBJPROP_XSIZE, g_editW - reserved_width); //--- Adjust edit field width
   int max_scroll = MathMax(0, totalHeight - visibleHeight); //--- Calculate maximum scroll distance
   if (p_scroll_pos > max_scroll) p_scroll_pos = max_scroll; //--- Clamp prompt scroll position
   if (p_scroll_pos < 0) p_scroll_pos = 0;   //--- Ensure prompt scroll position is non-negative
   if (p_scroll_visible) {                   //--- Check if prompt scrollbar is visible
      p_slider_height = CalculatePromptSliderHeight(); //--- Calculate prompt slider height
      ObjectSetInteger(0, P_SCROLL_SLIDER, OBJPROP_YSIZE, p_slider_height); //--- Update prompt slider size
      UpdatePromptSliderPosition();          //--- Update prompt slider position
      UpdatePromptButtonColors();            //--- Update prompt scrollbar button colors
   }
   int currentY = textY - p_scroll_pos;      //--- Calculate current y position
   int endY = textY + visibleHeight;         //--- Calculate end y position
   int startLineIndex = 0;                   //--- Initialize start line index
   int currentHeight = 0;                    //--- Initialize current height
   for (int line = 0; line < totalLines; line++) { //--- Iterate through lines
      if (currentHeight >= p_scroll_pos) {   //--- Check if line is in view
         startLineIndex = line;              //--- Set start line index
         currentY = textY + (currentHeight - p_scroll_pos); //--- Update current y position
         break;                              //--- Exit loop
      }
      currentHeight += adjustedLineHeight;   //--- Add line height
   }
   int numVisibleLines = 0;                  //--- Initialize visible lines count
   int visibleHeightUsed = 0;                //--- Initialize used visible height
   for (int line = startLineIndex; line < totalLines; line++) { //--- Iterate from start line
      if (visibleHeightUsed + adjustedLineHeight > visibleHeight) break; //--- Check if exceeds visible height
      visibleHeightUsed += adjustedLineHeight; //--- Add to used height
      numVisibleLines++;                     //--- Increment visible lines
   }
   int textX_pos = textX;                    //--- Set text x position
   int maxTextX = g_mainContentX + g_mainWidth - g_sidePadding - g_textPadding - reserved_width; //--- Calculate maximum text x position
   color textCol = clrBlack;                 //--- Set text color
   for (int li = 0; li < numVisibleLines; li++) { //--- Iterate through visible lines
      int lineIndex = startLineIndex + li;   //--- Calculate line index
      if (lineIndex >= totalLines) break;    //--- Check if index exceeds total lines
      string line = wrappedLines[lineIndex]; //--- Get line text
      string display_line = line;            //--- Initialize display line
      if (line == " ") {                     //--- Check if line is empty
         display_line = " ";                 //--- Set display line to space
         textCol = clrWhite;                 //--- Set text color to white
      }
      string lineName = "ChatGPT_PromptLine_" + IntegerToString(lineIndex); //--- Generate line object name
      if (currentY >= textY && currentY < endY) { //--- Check if line is visible
         createLabel(lineName, textX_pos, currentY, display_line, textCol, fontSize, font, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create line label
      }
      currentY += adjustedLineHeight;        //--- Update current y position
   }
   ChartRedraw();                            //--- Redraw chart
}

ここでは、複数行のユーザープロンプト表示を管理するUpdatePromptDisplay関数を実装します。これにより、スムーズなレンダリングとスクロールが可能になります。関数はまず、ObjectsTotalObjectDelete関数を使用して既存のChatGPT_PromptLine_オブジェクトを削除します。その後、g_mainContentX、g_sidePadding、g_promptHeight、g_textPaddingを用いてプロンプト領域のレイアウトを計算します。currentPromptが空の場合は、p_total_heightをリセットし、p_visible_heightを設定、DeletePromptScrollbarでスクロールバーを削除し、ObjectSetIntegerでChatGPT_PromptEditの幅を調整します。

プロンプトが空でない場合は、前に定義したWrapText関数を使用してテキストを行単位に折り返し、adjustedLineHeightからp_total_heightを算出します。その後、ScrollbarModeやmouse_in_promptに応じてスクロールバーの表示・非表示を動的に切り替え、必要に応じてreserved_widthでスペースを確保します。表示される行はcreateLabelでChatGPT_PromptLine_ラベルとして描画し、p_scroll_posで位置を更新し、ChartRedrawでチャートをリフレッシュして、複数行プロンプトのシームレスな操作を実現します。次に、チャートデータをプロンプトに追加するための関数を実装します。

//+------------------------------------------------------------------+
//| Convert Timeframe to String                                      |
//+------------------------------------------------------------------+
string PeriodToString(ENUM_TIMEFRAMES period) {
   switch(period) {                          //--- Switch on timeframe
      case PERIOD_M1: return "M1";           //--- Return M1 for 1-minute
      case PERIOD_M5: return "M5";           //--- Return M5 for 5-minute
      case PERIOD_M15: return "M15";         //--- Return M15 for 15-minute
      case PERIOD_M30: return "M30";         //--- Return M30 for 30-minute
      case PERIOD_H1: return "H1";           //--- Return H1 for 1-hour
      case PERIOD_H4: return "H4";           //--- Return H4 for 4-hour
      case PERIOD_D1: return "D1";           //--- Return D1 for daily
      case PERIOD_W1: return "W1";           //--- Return W1 for weekly
      case PERIOD_MN1: return "MN1";         //--- Return MN1 for monthly
      default: return IntegerToString(period); //--- Return period as string for others
   }
}

//+------------------------------------------------------------------+
//| Append Chart Data to Prompt                                      |
//+------------------------------------------------------------------+
void GetAndAppendChartData() {
   string symbol = Symbol();                  //--- Get current chart symbol
   ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)_Period; //--- Get current timeframe
   string timeframe = PeriodToString(tf);     //--- Convert timeframe to string
   long visibleBarsLong = ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get number of visible bars
   int visibleBars = (int)visibleBarsLong;   //--- Convert visible bars to integer
   MqlRates rates[];                         //--- Declare array for rate data
   int copied = CopyRates(symbol, tf, 0, MaxChartBars, rates); //--- Copy recent bar data
   if (copied != MaxChartBars) {             //--- Check if copy failed
      Print("Failed to copy rates: ", GetLastError()); //--- Log error
      return;                                //--- Exit function
   }
   ArraySetAsSeries(rates, true);            //--- Set rates as time series
   string data = "Chart Details: Symbol=" + symbol + ", Timeframe=" + timeframe + ", Visible Bars=" + IntegerToString(visibleBars) + "\n"; //--- Build chart details string
   data += "Recent Bars Data (Bar 1 is latest):\n"; //--- Add header for bar data
   for (int i = 0; i < copied; i++) {       //--- Iterate through copied bars
      data += "Bar " + IntegerToString(i + 1) + ": Date=" + TimeToString(rates[i].time, TIME_DATE | TIME_MINUTES) + ", Open=" + DoubleToString(rates[i].open, _Digits) + ", High=" + DoubleToString(rates[i].high, _Digits) + ", Low=" + DoubleToString(rates[i].low, _Digits) + ", Close=" + DoubleToString(rates[i].close, _Digits) + ", Volume=" + IntegerToString((int)rates[i].tick_volume) + "\n"; //--- Add bar data
   }
   Print("Chart data appended to prompt: \n" + data); //--- Log chart data
   FileWrite(logFileHandle, "Chart data appended to prompt: \n" + data); //--- Write chart data to log
   string fileName = "candlesticksdata.txt"; //--- Set file name for chart data
   int handle = FileOpen(fileName, FILE_WRITE | FILE_TXT | FILE_ANSI); //--- Open file for writing
   if (handle == INVALID_HANDLE) {           //--- Check if file opening failed
      Print("Failed to open file for writing: ", GetLastError()); //--- Log error
      return;                                //--- Exit function
   }
   FileWriteString(handle, data);            //--- Write chart data to file
   FileClose(handle);                        //--- Close file
   handle = FileOpen(fileName, FILE_READ | FILE_TXT | FILE_ANSI); //--- Open file for reading
   if (handle == INVALID_HANDLE) {           //--- Check if file opening failed
      Print("Failed to open file for reading: ", GetLastError()); //--- Log error
      return;                                //--- Exit function
   }
   string fileContent = "";                  //--- Initialize file content string
   while (!FileIsEnding(handle)) {           //--- Loop until end of file
      fileContent += FileReadString(handle) + "\n"; //--- Read and append line
   }
   FileClose(handle);                        //--- Close file
   if (StringLen(currentPrompt) > 0) {       //--- Check if prompt is non-empty
      currentPrompt += "\n";                 //--- Append newline to prompt
   }
   currentPrompt += fileContent;             //--- Append chart data to prompt
   DeletePlaceholder();                      //--- Delete prompt placeholder
   UpdatePromptDisplay();                    //--- Update prompt display
   p_scroll_pos = MathMax(0, p_total_height - p_visible_height); //--- Set prompt scroll to bottom
   if (p_scroll_visible) {                   //--- Check if prompt scrollbar is visible
      UpdatePromptSliderPosition();          //--- Update prompt slider position
      UpdatePromptButtonColors();            //--- Update prompt scrollbar button colors
   }
   ChartRedraw();                            //--- Redraw chart
}

チャートデータ統合を実装するために、まずPeriodToString関数を定義します。この関数では、PERIOD_M1やPERIOD_H1のような時間足列挙型を、switch文を用いて「M1」や「H1」のように読みやすい文字列に変換し、チャート期間を明確に伝えられるようにします。次に、GetAndAppendChartData関数を定義します。この関数では、Symbolで現在のチャートの通貨ペアを取得し、_Periodで時間足、ChartGetIntegerで表示されているバー数を取得します。その後、CopyRatesを使用して最近のバー情報をMqlRates配列に取得し、TimeToStringDoubleToStringで始値、最高値、最安値、終値、出来高などの詳細を文字列に整形します。

取得したデータはログに出力し、FileWriteStringでcandlesticksdata.txtに保存、FileReadStringで読み戻してcurrentPromptに追記し、AI処理用に準備します。その後、DeletePlaceholder、UpdatePromptDisplayを呼び出してプロンプト領域に表示し、UpdatePromptSliderPositionとUpdatePromptButtonColorsでスクロールバーの表示を更新します。これにより、チャートデータ送信ボタンをクリックした際、データを先に取得し、保存してから処理できるようになります。下記に手順を示します。

バーデータの格納

会話履歴からメッセージを構築するため、AIに送信する新しいチャートデータを考慮できるように関数を拡張する必要があります。チャートデータは新しいフォーマットを持つため、役割ごとのすべての内容を考慮するように処理をおこないます。

//+------------------------------------------------------------------+
//| Build JSON Messages from History                                 |
//+------------------------------------------------------------------+
string BuildMessagesFromHistory(string newPrompt) {
   string lines[];                           //--- Declare array for history lines
   int numLines = StringSplit(conversationHistory, '\n', lines); //--- Split history into lines
   string messages = "[";                    //--- Initialize JSON messages array
   string currentRole = "";                  //--- Initialize current role
   string currentContent = "";               //--- Initialize current content
   for (int i = 0; i < numLines; i++) {     //--- Iterate through history lines
      string line = lines[i];                //--- Get current line
      string trimmed = line;                 //--- Copy line for trimming
      StringTrimLeft(trimmed);               //--- Remove leading whitespace
      StringTrimRight(trimmed);              //--- Remove trailing whitespace
      if (StringLen(trimmed) == 0 || IsTimestamp(trimmed)) continue; //--- Skip empty or timestamp lines
      if (StringFind(trimmed, "You: ") == 0) { //--- Check if user message
         if (currentRole != "") {            //--- Check if previous message exists
            string roleJson = (currentRole == "User") ? "user" : "assistant"; //--- Set JSON role
            messages += "{\"role\":\"" + roleJson + "\",\"content\":\"" + JsonEscape(currentContent) + "\"},"; //--- Add message to JSON
         }
         currentRole = "User";              //--- Set role to user
         currentContent = StringSubstr(line, StringFind(line, "You: ") + 5); //--- Extract user message
      } else if (StringFind(trimmed, "AI: ") == 0) { //--- Check if AI message
         if (currentRole != "") {            //--- Check if previous message exists
            string roleJson = (currentRole == "User") ? "user" : "assistant"; //--- Set JSON role
            messages += "{\"role\":\"" + roleJson + "\",\"content\":\"" + JsonEscape(currentContent) + "\"},"; //--- Add message to JSON
         }
         currentRole = "AI";                //--- Set role to AI
         currentContent = StringSubstr(line, StringFind(line, "AI: ") + 4); //--- Extract AI message
      } else if (currentRole != "") {       //--- Handle continuation line
         currentContent += "\n" + line;     //--- Append line to content
      }
   }
   if (currentRole != "") {                  //--- Check if final message exists
      string roleJson = (currentRole == "User") ? "user" : "assistant"; //--- Set JSON role
      messages += "{\"role\":\"" + roleJson + "\",\"content\":\"" + JsonEscape(currentContent) + "\"},"; //--- Add final message to JSON
   }
   messages += "{\"role\":\"user\",\"content\":\"" + JsonEscape(newPrompt) + "\"}]"; //--- Add new prompt to JSON
   return messages;                          //--- Return JSON messages
}

BuildMessagesFromHistory関数を拡張し、OpenAI APIリクエスト用に会話データを整形します。conversationHistory文字列を改行で区切ってStringSplitで分割し、各行をStringTrimLeftStringTrimRightで前後の空白を削除します。空行やIsTimestampで判定されるタイムスタンプ行はスキップします。ユーザーメッセージは「You: 」で、AIメッセージは「AI: 」で始まるものとしてStringFindで判定し、StringSubstrで内容を抽出します。その後、各メッセージを役割(userまたはassistant)とともにJSONオブジェクトとして配列messagesに追加し、JsonEscapeで内容をエスケープします。新しいプロンプトは最後のユーザーメッセージとして含めます。次に、サイドバーを扱い、必要な要素を更新して永続的なチャットを管理できるようにします。まずチャットのロジックを定義し、それを使ってナビゲーションバー全体を描画できるようにします。

//+------------------------------------------------------------------+
//| Chat Structure Definition                                        |
//+------------------------------------------------------------------+
struct Chat {
   int id;                                        //--- Store chat ID
   string title;                                  //--- Store chat title
   string history;                                //--- Store chat history
};
Chat chats[];                                     //--- Declare array for chat storage
int current_chat_id = -1;                         //--- Store current chat ID
string current_title = "";                        //--- Store current chat title
string chatsFileName = "ChatGPT_Chats.txt";       //--- Set file name for chat storage

//+------------------------------------------------------------------+
//| Encode Chat ID to Base62                                         |
//+------------------------------------------------------------------+
string EncodeID(int id) {
   string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; //--- Define base62 character set
   string res = "";                                //--- Initialize result string
   if (id == 0) return "0";                        //--- Return "0" for zero ID
   while (id > 0) {                                //--- Loop while ID is positive
      res = StringSubstr(chars, id % 62, 1) + res; //--- Prepend base62 character
      id /= 62;                                    //--- Divide ID by 62
   }
   return res;                                     //--- Return encoded ID
}

//+------------------------------------------------------------------+
//| Decode Base62 Chat ID                                            |
//+------------------------------------------------------------------+
int DecodeID(string enc) {
   string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; //--- Define base62 character set
   int id = 0;                                     //--- Initialize decoded ID
   for (int i = 0; i < StringLen(enc); i++) {      //--- Iterate through encoded string
      id = id * 62 + StringFind(chars, StringSubstr(enc, i, 1)); //--- Calculate ID value
   }
   return id;                                      //--- Return decoded ID
}

//+------------------------------------------------------------------+
//| Load Chats from File                                             |
//+------------------------------------------------------------------+
void LoadChats() {
   if (!FileIsExist(chatsFileName)) {              //--- Check if chats file exists
      CreateNewChat();                             //--- Create new chat if file not found
      return;                                      //--- Exit function
   }
   int handle = FileOpen(chatsFileName, FILE_READ | FILE_BIN); //--- Open chats file for reading
   if (handle == INVALID_HANDLE) {                 //--- Check if file opening failed
      Print("Failed to load chats: ", GetLastError()); //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   int file_size = (int)FileSize(handle);          //--- Get file size
   uchar encoded_file[];                           //--- Declare array for encoded file data
   ArrayResize(encoded_file, file_size);           //--- Resize array to file size
   FileReadArray(handle, encoded_file, 0, file_size); //--- Read file data into array
   FileClose(handle);                              //--- Close file
   uchar empty_key[];                              //--- Declare empty key array
   uchar key[32];                                  //--- Declare key array for decryption
   uchar api_bytes[];                              //--- Declare array for API key bytes
   StringToCharArray(OpenAI_API_Key, api_bytes);   //--- Convert API key to byte array
   uchar hash[];                                   //--- Declare array for hash
   CryptEncode(CRYPT_HASH_SHA256, api_bytes, empty_key, hash); //--- Generate SHA256 hash of API key
   ArrayCopy(key, hash, 0, 0, 32);                 //--- Copy first 32 bytes of hash to key
   uchar decoded_aes[];                            //--- Declare array for AES-decrypted data
   int res_dec = CryptDecode(CRYPT_AES256, encoded_file, key, decoded_aes); //--- Decrypt file data with AES256
   if (res_dec <= 0) {                             //--- Check if decryption failed
      Print("Failed to decrypt chats: ", GetLastError()); //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   uchar decoded_zip[];                            //--- Declare array for unzipped data
   int res_zip = CryptDecode(CRYPT_ARCH_ZIP, decoded_aes, empty_key, decoded_zip); //--- Decompress decrypted data
   if (res_zip <= 0) {                             //--- Check if decompression failed
      Print("Failed to decompress chats: ", GetLastError()); //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   string jsonStr = CharArrayToString(decoded_zip); //--- Convert decompressed data to string
   char charArray[];                               //--- Declare array for JSON characters
   int len = StringToCharArray(jsonStr, charArray, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert JSON string to char array
   JsonValue json;                                 //--- Declare JSON object
   int index = 0;                                  //--- Initialize JSON parsing index
   if (!json.DeserializeFromArray(charArray, len, index)) { //--- Parse JSON data
      Print("Failed to parse chats JSON");         //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   if (json.m_type != JsonArray) {                 //--- Check if JSON is an array
      Print("Chats JSON not an array");            //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   int size = ArraySize(json.m_children);          //--- Get number of chat objects
   ArrayResize(chats, size);                       //--- Resize chats array
   int max_id = 0;                                 //--- Initialize maximum chat ID
   for (int i = 0; i < size; i++) {                //--- Iterate through chat objects
      JsonValue obj = json.m_children[i];          //--- Get current chat object
      chats[i].id = (int)obj["id"].ToInteger();    //--- Set chat ID
      chats[i].title = obj["title"].ToString();    //--- Set chat title
      chats[i].history = obj["history"].ToString(); //--- Set chat history
      max_id = MathMax(max_id, chats[i].id);       //--- Update maximum chat ID
   }
   if (size > 0) {                                 //--- Check if chats exist
      current_chat_id = chats[size - 1].id;        //--- Set current chat ID to last chat
      current_title = chats[size - 1].title;       //--- Set current chat title
      conversationHistory = chats[size - 1].history; //--- Set current conversation history
   } else {                                        //--- No chats found
      CreateNewChat();                             //--- Create new chat
   }
}

//+------------------------------------------------------------------+
//| Save Chats to File                                               |
//+------------------------------------------------------------------+
void SaveChats() {
   JsonValue jsonArr;                            //--- Declare JSON array
   jsonArr.m_type = JsonArray;                   //--- Set JSON type to array
   for (int i = 0; i < ArraySize(chats); i++) {  //--- Iterate through chats
      JsonValue obj;                             //--- Declare JSON object
      obj.m_type = JsonObject;                   //--- Set JSON type to object
      obj["id"] = chats[i].id;                   //--- Set chat ID in JSON
      obj["title"] = chats[i].title;             //--- Set chat title in JSON
      obj["history"] = chats[i].history;         //--- Set chat history in JSON
      jsonArr.AddChild(obj);                     //--- Add object to JSON array
   }
   string jsonStr = jsonArr.SerializeToString(); //--- Serialize JSON array to string
   uchar data[];                                 //--- Declare array for JSON data
   StringToCharArray(jsonStr, data);             //--- Convert JSON string to byte array
   uchar empty_key[];                            //--- Declare empty key array
   uchar zipped[];                               //--- Declare array for zipped data
   int res_zip = CryptEncode(CRYPT_ARCH_ZIP, data, empty_key, zipped); //--- Compress JSON data
   if (res_zip <= 0) {                           //--- Check if compression failed
      Print("Failed to compress chats: ", GetLastError()); //--- Log error
      return;                                    //--- Exit function
   }
   uchar key[32];                                //--- Declare key array for encryption
   uchar api_bytes[];                            //--- Declare array for API key bytes
   StringToCharArray(OpenAI_API_Key, api_bytes); //--- Convert API key to byte array
   uchar hash[];                                 //--- Declare array for hash
   CryptEncode(CRYPT_HASH_SHA256, api_bytes, empty_key, hash); //--- Generate SHA256 hash of API key
   ArrayCopy(key, hash, 0, 0, 32);               //--- Copy first 32 bytes of hash to key
   uchar encoded[];                              //--- Declare array for encrypted data
   int res_enc = CryptEncode(CRYPT_AES256, zipped, key, encoded); //--- Encrypt compressed data with AES256
   if (res_enc <= 0) {                           //--- Check if encryption failed
      Print("Failed to encrypt chats: ", GetLastError()); //--- Log error
      return;                                    //--- Exit function
   }
   int handle = FileOpen(chatsFileName, FILE_WRITE | FILE_BIN); //--- Open chats file for writing
   if (handle == INVALID_HANDLE) {               //--- Check if file opening failed
      Print("Failed to save chats: ", GetLastError()); //--- Log error
      return;                                    //--- Exit function
   }
   FileWriteArray(handle, encoded, 0, res_enc);  //--- Write encrypted data to file
   FileClose(handle);                            //--- Close file
}

//+------------------------------------------------------------------+
//| Get Chat Index by ID                                             |
//+------------------------------------------------------------------+
int GetChatIndex(int id) {
   for (int i = 0; i < ArraySize(chats); i++) {  //--- Iterate through chats
      if (chats[i].id == id) return i;           //--- Return index if ID matches
   }
   return -1;                                    //--- Return -1 if ID not found
}

//+------------------------------------------------------------------+
//| Create New Chat                                                  |
//+------------------------------------------------------------------+
void CreateNewChat() {
   int max_id = 0;                               //--- Initialize maximum chat ID
   for (int i = 0; i < ArraySize(chats); i++) {  //--- Iterate through chats
      max_id = MathMax(max_id, chats[i].id);     //--- Update maximum chat ID
   }
   int new_id = max_id + 1;                      //--- Calculate new chat ID
   int size = ArraySize(chats);                  //--- Get current chats array size
   ArrayResize(chats, size + 1);                 //--- Resize chats array
   chats[size].id = new_id;                      //--- Set new chat ID
   chats[size].title = "Chat " + IntegerToString(new_id); //--- Set new chat title
   chats[size].history = "";                     //--- Initialize empty chat history
   current_chat_id = new_id;                     //--- Set current chat ID
   current_title = chats[size].title;            //--- Set current chat title
   conversationHistory = "";                     //--- Clear current conversation history
   SaveChats();                                  //--- Save chats to file
   UpdateSidebarDynamic();                       //--- Update sidebar display
   UpdateResponseDisplay();                      //--- Update response display
   UpdatePromptDisplay();                        //--- Update prompt display
   CreatePlaceholder();                          //--- Create prompt placeholder
   ChartRedraw();                                //--- Redraw chart to reflect changes
}

//+------------------------------------------------------------------+
//| Update Current Chat History                                      |
//+------------------------------------------------------------------+
void UpdateCurrentHistory() {
   int idx = GetChatIndex(current_chat_id);      //--- Get index of current chat
   if (idx >= 0) {                               //--- Check if valid index
      chats[idx].history = conversationHistory;  //--- Update chat history
      chats[idx].title = current_title;          //--- Update chat title
      SaveChats();                               //--- Save chats to file
   }
}

ここでは、永続的なチャット保存および管理関数を実装し、セッションをまたいで会話履歴を保持できるようにします。これにより、サイドバーを通じてシームレスにナビゲーションできるようになります。まず、Chat構造体を定義し、各チャットのid、title、historyを保持します。chats配列、アクティブセッションを追跡するcurrent_chat_idおよびcurrent_title、保存用のchatsFileName (ChatGPT_Chats.txt)を設定します。EncodeIDとDecodeID関数では、チャットIDをbase62形式に変換し、StringSubstrで短く表示してサイドバーに収めます。LoadChatsでは、FileOpenでChatGPT_Chats.txtを読み込み、CryptDecode (CRYPT_AES256)で復号化します。鍵はOpenAI_API_KeyからCryptEncode(CRYPT_HASH_SHA256)で生成します。さらにCRYPT_ARCH_ZIPで解凍し、DeserializeFromArrayでJSONを解析してchats配列に格納します。エラーが発生した場合はCreateNewChatで新規チャットを作成します。

SaveChatsでは、chats配列をSerializeToStringでJSON化し、CryptEncode(CRYPT_ARCH_ZIP)で圧縮、CRYPT_AES256で暗号化した上で、FileWriteArrayでChatGPT_Chats.txtに保存します。GetChatIndexはIDからチャットを検索する関数で、ArraySizeを使用します。CreateNewChatは新しいチャットを初期化し、インクリメントされたIDを付与、current_chat_id、current_title、conversationHistoryを更新、SaveChatsで保存し、UpdateSidebarDynamic、UpdateResponseDisplay、UpdatePromptDisplayでUIを更新します。

UpdateCurrentHistory関数は、現在のチャットのhistoryとtitleをchats配列に更新し、ファイルに保存します。これにより、永続的でナビゲーション可能なチャットデータが確保されます。デコードおよびエンコード方式の選択は任意で、ここでは簡単さを重視して最も扱いやすい方法を採用しています。これらの関数を備えたことで、次にサイドバーを更新するロジックを定義できます。

//+------------------------------------------------------------------+
//| Update Sidebar Dynamically                                       |
//+------------------------------------------------------------------+
void UpdateSidebarDynamic() {
   int total = ObjectsTotal(0, 0, -1);           //--- Get total number of chart objects
   for (int j = total - 1; j >= 0; j--) {       //--- Iterate through objects in reverse
      string name = ObjectName(0, j, 0, -1);     //--- Get object name
      if (StringFind(name, "ChatGPT_NewChatButton") == 0 || StringFind(name, "ChatGPT_ClearButton") == 0 || StringFind(name, "ChatGPT_HistoryButton") == 0 || StringFind(name, "ChatGPT_ChatLabel_") == 0 || StringFind(name, "ChatGPT_ChatBg_") == 0 || StringFind(name, "ChatGPT_SidebarLogo") == 0 || StringFind(name, "ChatGPT_NewChatIcon") == 0 || StringFind(name, "ChatGPT_NewChatLabel") == 0 || StringFind(name, "ChatGPT_ClearIcon") == 0 || StringFind(name, "ChatGPT_ClearLabel") == 0 || StringFind(name, "ChatGPT_HistoryIcon") == 0 || StringFind(name, "ChatGPT_HistoryLabel") == 0) { //--- Check if object is part of sidebar
         ObjectDelete(0, name);                  //--- Delete sidebar object
      }
   }
   int sidebarX = g_dashboardX;                   //--- Set sidebar x position
   int itemY = g_mainY + 10;                     //--- Set initial item y position
   string sidebar_logo_resource = (StringLen(g_scaled_sidebar_resource) > 0) ? g_scaled_sidebar_resource : resourceImgLogo; //--- Select sidebar logo resource
   createBitmapLabel("ChatGPT_SidebarLogo", sidebarX + (g_sidebarWidth - 81)/2, itemY, 81, 81, sidebar_logo_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create sidebar logo
   ObjectSetInteger(0, "ChatGPT_SidebarLogo", OBJPROP_ZORDER, 1); //--- Set logo z-order
   itemY += 81 + 10;                             //--- Update item y position
   createButton("ChatGPT_NewChatButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, new_chat_original_bg, clrRoyalBlue); //--- Create new chat button
   ObjectSetInteger(0, "ChatGPT_NewChatButton", OBJPROP_ZORDER, 1); //--- Set new chat button z-order
   string newchat_icon_resource = (StringLen(g_scaled_newchat_resource) > 0) ? g_scaled_newchat_resource : resourceNewChat; //--- Select new chat icon resource
   createBitmapLabel("ChatGPT_NewChatIcon", sidebarX + 5 + 10, itemY + (g_buttonHeight - 30)/2, 30, 30, newchat_icon_resource, clrNONE, CORNER_LEFT_UPPER); //--- Create new chat icon
   ObjectSetInteger(0, "ChatGPT_NewChatIcon", OBJPROP_ZORDER, 2); //--- Set new chat icon z-order
   ObjectSetInteger(0, "ChatGPT_NewChatIcon", OBJPROP_SELECTABLE, false); //--- Disable new chat icon selectability
   createLabel("ChatGPT_NewChatLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "New Chat", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); //--- Create new chat label
   ObjectSetInteger(0, "ChatGPT_NewChatLabel", OBJPROP_ZORDER, 2); //--- Set new chat label z-order
   ObjectSetInteger(0, "ChatGPT_NewChatLabel", OBJPROP_SELECTABLE, false); //--- Disable new chat label selectability
   itemY += g_buttonHeight + 5;                  //--- Update item y position
   createButton("ChatGPT_ClearButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, clear_original_bg, clrIndianRed); //--- Create clear button
   ObjectSetInteger(0, "ChatGPT_ClearButton", OBJPROP_ZORDER, 1); //--- Set clear button z-order
   string clear_icon_resource = (StringLen(g_scaled_clear_resource) > 0) ? g_scaled_clear_resource : resourceClear; //--- Select clear icon resource
   createBitmapLabel("ChatGPT_ClearIcon", sidebarX + 5 + 10, itemY + (g_buttonHeight - 30)/2, 30, 30, clear_icon_resource, clrNONE, CORNER_LEFT_UPPER); //--- Create clear icon
   ObjectSetInteger(0, "ChatGPT_ClearIcon", OBJPROP_ZORDER, 2); //--- Set clear icon z-order
   ObjectSetInteger(0, "ChatGPT_ClearIcon", OBJPROP_SELECTABLE, false); //--- Disable clear icon selectability
   createLabel("ChatGPT_ClearLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "Clear", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); //--- Create clear label
   ObjectSetInteger(0, "ChatGPT_ClearLabel", OBJPROP_ZORDER, 2); //--- Set clear label z-order
   ObjectSetInteger(0, "ChatGPT_ClearLabel", OBJPROP_SELECTABLE, false); //--- Disable clear label selectability
   itemY += g_buttonHeight + 10;                 //--- Update item y position
   createButton("ChatGPT_HistoryButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrBlack, 12, clrWhite, clrGray); //--- Create history button
   ObjectSetInteger(0, "ChatGPT_HistoryButton", OBJPROP_ZORDER, 1); //--- Set history button z-order
   string history_icon_resource = (StringLen(g_scaled_history_resource) > 0) ? g_scaled_history_resource : resourceHistory; //--- Select history icon resource
   createBitmapLabel("ChatGPT_HistoryIcon", sidebarX + 5 + 10, itemY + (g_buttonHeight - 30)/2, 30, 30, history_icon_resource, clrNONE, CORNER_LEFT_UPPER); //--- Create history icon
   ObjectSetInteger(0, "ChatGPT_HistoryIcon", OBJPROP_ZORDER, 2); //--- Set history icon z-order
   ObjectSetInteger(0, "ChatGPT_HistoryIcon", OBJPROP_SELECTABLE, false); //--- Disable history icon selectability
   createLabel("ChatGPT_HistoryLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "History", clrBlack, 12, "Arial", CORNER_LEFT_UPPER); //--- Create history label
   ObjectSetInteger(0, "ChatGPT_HistoryLabel", OBJPROP_ZORDER, 2); //--- Set history label z-order
   ObjectSetInteger(0, "ChatGPT_HistoryLabel", OBJPROP_SELECTABLE, false); //--- Disable history label selectability
   itemY += g_buttonHeight + 5;                  //--- Update item y position
   int numChats = MathMin(ArraySize(chats), 7);  //--- Limit number of chats to display
   int chatIndices[7];                           //--- Declare array for chat indices
   for (int i = 0; i < numChats; i++) {          //--- Iterate to set chat indices
      chatIndices[i] = ArraySize(chats) - 1 - i; //--- Set index for latest chats first
   }
   for (int i = 0; i < numChats; i++) {          //--- Iterate through chats to display
      int chatIdx = chatIndices[i];              //--- Get chat index
      string hashed_id = EncodeID(chats[chatIdx].id); //--- Encode chat ID to base62
      string fullText = chats[chatIdx].title + " > " + hashed_id; //--- Create full chat title text
      string labelText = fullText;               //--- Initialize label text
      if (StringLen(fullText) > 19) {            //--- Check if text exceeds length limit
         labelText = StringSubstr(fullText, 0, 16) + "..."; //--- Truncate text with ellipsis
      }
      string bgName = "ChatGPT_ChatBg_" + hashed_id; //--- Generate background object name
      string labelName = "ChatGPT_ChatLabel_" + hashed_id; //--- Generate label object name
      color bgColor = clrWhite;                  //--- Set background color
      color borderColor = clrGray;               //--- Set border color
      createRecLabel(bgName, sidebarX + 5 + 10, itemY, g_sidebarWidth - 10 - 10, 25, clrBeige, 1, DarkenColor(clrBeige, 9), BORDER_FLAT, STYLE_SOLID); //--- Create chat background rectangle
      ObjectSetInteger(0, bgName, OBJPROP_ZORDER, 1); //--- Set background z-order
      color textColor = (chats[chatIdx].id == current_chat_id) ? clrBlue : clrBlack; //--- Set text color based on selection
      createLabel(labelName, sidebarX + 10 + 10, itemY + 3, labelText, textColor, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create chat label
      ObjectSetInteger(0, labelName, OBJPROP_ZORDER, 2); //--- Set label z-order
      itemY += 25 + 5;                           //--- Update item y position
   }
   ChartRedraw();                                //--- Redraw chart to reflect changes
}

UpdateSidebarDynamic関数を実装し、作成した永続的チャット履歴をナビゲートするための動的サイドバーを作成します。まず、ObjectsTotal、ObjectName、ObjectDeleteを使用して既存のサイドバーオブジェクト(ChatGPT_NewChatButton、ChatGPT_ClearButton、ChatGPT_HistoryButton、ChatGPT_ChatLabel_、ChatGPT_SidebarLogo)を、StringFindで判定しながら削除します。その後、g_dashboardXの位置にサイドバーを再構築し、createBitmapLabelでロゴChatGPT_SidebarLogoをg_scaled_sidebar_resourceまたはresourceImgLogoを用いて表示します。

次に、ボタンChatGPT_NewChatButton、ChatGPT_ClearButton、ChatGPT_HistoryButtonをcreateButtonで追加し、アイコンChatGPT_NewChatIcon、ChatGPT_ClearIcon、ChatGPT_HistoryIconをcreateBitmapLabelで作成、ラベルChatGPT_NewChatLabel、ChatGPT_ClearLabel、ChatGPT_HistoryLabelをcreateLabelで作成します。OBJPROP_ZORDERを設定し、OBJPROP_SELECTABLEで選択不可にします。chats配列から最大7件の最近のチャットについて、EncodeIDでIDを符号化し、createRecLabelとcreateLabelでChatGPT_ChatBg_とChatGPT_ChatLabel_オブジェクトを作成します。必要に応じてStringSubstrでタイトルを切り詰め、current_chat_idに基づきアクティブチャットをclrBlueでハイライトします。最後にChartRedrawで表示を更新し、シームレスなサイドバー体験を提供します。初期化時にこの関数を呼び出すと、以下のような表示が得られます。

完全に更新されたサイドバー

サイドバーが完全に更新されたので、準備は整いました。後は、OnDeinitイベントハンドラで、必要に応じて作成した要素を適切に処理するだけです。

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   UpdateCurrentHistory();                           //--- Update current chat history
   ObjectsDeleteAll(0, "ChatGPT_");                  //--- Delete all ChatGPT objects
   DeleteScrollbar();                                //--- Delete main scrollbar elements
   DeletePromptScrollbar();                          //--- Delete prompt scrollbar elements
   if (StringLen(g_scaled_image_resource) > 0) {     //--- Check if main image resource exists
      ResourceFree(g_scaled_image_resource);         //--- Free main image resource
   }
   if (StringLen(g_scaled_sidebar_resource) > 0) {   //--- Check if sidebar image resource exists
      ResourceFree(g_scaled_sidebar_resource);       //--- Free sidebar image resource
   }
   if (StringLen(g_scaled_newchat_resource) > 0) {   //--- Check if new chat icon resource exists
      ResourceFree(g_scaled_newchat_resource);       //--- Free new chat icon resource
   }
   if (StringLen(g_scaled_clear_resource) > 0) {     //--- Check if clear icon resource exists
      ResourceFree(g_scaled_clear_resource);         //--- Free clear icon resource
   }
   if (StringLen(g_scaled_history_resource) > 0) {   //--- Check if history icon resource exists
      ResourceFree(g_scaled_history_resource);       //--- Free history icon resource
   }
   if (logFileHandle != INVALID_HANDLE) {            //--- Check if log file is open
      FileClose(logFileHandle);                      //--- Close log file
   }
}

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick() {
}

//+------------------------------------------------------------------+
//| Hide Dashboard                                                   |
//+------------------------------------------------------------------+
void HideDashboard() {
   dashboard_visible = false;                        //--- Set dashboard visibility to false
   for (int i = 0; i < objCount; i++) {              //--- Iterate through dashboard objects
      ObjectDelete(0, dashboardObjects[i]);          //--- Delete dashboard object
   }
   DeleteScrollbar();                                //--- Delete main scrollbar elements
   DeletePromptScrollbar();                          //--- Delete prompt scrollbar elements
   ObjectDelete(0, "ChatGPT_DashboardBg");           //--- Delete dashboard background
   ObjectDelete(0, "ChatGPT_SidebarBg");             //--- Delete sidebar background
   ChartRedraw();                                    //--- Redraw chart to reflect changes
}

OnDeinit関数では、まずUpdateCurrentHistory関数を呼び出して現在のチャット状態を保存します。その後、ObjectsDeleteAllでChatGPT_プレフィックスのオブジェクトをすべて削除し、DeleteScrollbarとDeletePromptScrollbarでスクロールバーも削除します。スケーリング済みの画像リソース(g_scaled_image_resource、g_scaled_sidebar_resource、g_scaled_newchat_resource、g_scaled_clear_resource、g_scaled_history_resource)が存在する場合はResourceFreeで解放し、logFileHandleをFileCloseで閉じてリソースリークを防ぎます。

OnTick関数は現在イベント駆動の更新に依存しているため空のままです。HideDashboard関数では、dashboard_visibleをfalseに設定し、dashboardObjects内のすべてのオブジェクトをObjectDeleteで削除します。また、ChatGPT_DashboardBg、ChatGPT_SidebarBgおよびスクロールバーをDeleteScrollbarとDeletePromptScrollbarで削除し、ChartRedrawでチャートを更新してUIをシームレスに非表示にします。この関数はチャットを閉じるボタンをクリックした際に呼び出されます。さらに、プロンプト送信ボタンをクリックした際には、チャートデータを追記する仕様に伴い、プロンプト送信関数を更新する必要があります。以下がそのロジックです。

//+------------------------------------------------------------------+
//| Submit User Prompt and Handle Response                           |
//+------------------------------------------------------------------+
void SubmitMessage(string prompt) {
   if (StringLen(prompt) == 0) return;       //--- Exit if prompt is empty
   string timestamp = TimeToString(TimeCurrent(), TIME_MINUTES); //--- Get current time as string
   string response = "";                     //--- Initialize response string
   bool send_to_api = true;                  //--- Set flag to send to API
   if (StringFind(prompt, "set title ") == 0) { //--- Check if prompt is a title change
      string new_title = StringSubstr(prompt, 10); //--- Extract new title
      current_title = new_title;             //--- Set current chat title
      response = "Title set to " + new_title; //--- Set response message
      send_to_api = false;                   //--- Disable API call
      UpdateCurrentHistory();                //--- Update current chat history
      UpdateSidebarDynamic();                //--- Update sidebar display
   }
   if (send_to_api) {                        //--- Check if API call is needed
      Print("Chat ID: " + IntegerToString(current_chat_id) + ", Title: " + current_title); //--- Log chat ID and title
      FileWrite(logFileHandle, "Chat ID: " + IntegerToString(current_chat_id) + ", Title: " + current_title); //--- Write chat ID and title to log
      Print("User: " + prompt);              //--- Log user prompt
      FileWrite(logFileHandle, "User: " + prompt); //--- Write user prompt to log
      response = GetChatGPTResponse(prompt); //--- Get response from ChatGPT API
      Print("AI: " + response);              //--- Log AI response
      FileWrite(logFileHandle, "AI: " + response); //--- Write AI response to log
      if (StringFind(current_title, "Chat ") == 0) { //--- Check if title is default
         current_title = StringSubstr(prompt, 0, 30); //--- Set title to first 30 characters of prompt
         if (StringLen(prompt) > 30) current_title += "..."; //--- Append ellipsis if truncated
         UpdateCurrentHistory();             //--- Update current chat history
         UpdateSidebarDynamic();             //--- Update sidebar display
      }
   }
   conversationHistory += "You: " + prompt + "\n" + timestamp + "\nAI: " + response + "\n" + timestamp + "\n\n"; //--- Append to conversation history
   UpdateCurrentHistory();                   //--- Update current chat history
   UpdateResponseDisplay();                  //--- Update response display
   scroll_pos = MathMax(0, g_total_height - g_visible_height); //--- Set scroll position to bottom
   UpdateResponseDisplay();                  //--- Update response display again
   if (scroll_visible) {                     //--- Check if main scrollbar is visible
      UpdateSliderPosition();                //--- Update main slider position
      UpdateButtonColors();                  //--- Update main scrollbar button colors
   }
   ChartRedraw();                            //--- Redraw chart
}

SubmitMessage関数では、ユーザープロンプトにチャートデータを組み込み、AI応答を統合できるように更新し、カスタムチャットタイトルや会話の永続化にも対応します。まず、StringLenでpromptが空かをチェックし、空の場合は処理を終了します。空でない場合は、TimeToStringで現在のタイムスタンプを取得します。プロンプトが「set title 」で始まる場合は、StringFindで判定し、StringSubstrで新しいタイトルを抽出してcurrent_titleを更新し、ローカルのresponseを設定して、API呼び出しをおこなわずにUpdateCurrentHistoryとUpdateSidebarDynamicを呼び出します。それ以外の場合は、PrintとFileWriteでcurrent_chat_idとcurrent_titleをログに出力し、GetChatGPTResponseでAI応答を取得します。デフォルトタイトルの場合はプロンプトの最初の30文字でタイトルを更新し、タイムスタンプとともにconversationHistoryにプロンプトと応答を追記します。その後、UpdateResponseDisplay、UpdateSliderPosition、UpdateButtonColorsを呼び出して、scroll_posで画面を下までスクロールし、描画を更新します。

これで、チャートとのインタラクションに関する最終部分も既存の構造と同様の形式で更新できます。ここでは、今回導入したチャット履歴の最も重要な部分のみを説明し、その他は既存の処理と同じです。

//+------------------------------------------------------------------+
//| Handle Chart Events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   int displayX = g_mainContentX + g_sidePadding;   //--- Calculate main display x position
   int displayY = g_mainY + g_headerHeight + g_padding; //--- Calculate main display y position
   int displayW = g_mainWidth - 2 * g_sidePadding;  //--- Calculate main display width
   int displayH = g_displayHeight;                  //--- Set main display height
   int footerY = displayY + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;                //--- Calculate prompt y position
   int promptH = g_promptHeight;                    //--- Set prompt height
   int closeX = g_mainContentX + g_mainWidth - 100 - g_sidePadding; //--- Calculate close button x position
   int closeY = g_mainY + 4;                        //--- Calculate close button y position
   int closeW = 100;                                //--- Set close button width
   int closeH = g_headerHeight - 8;                 //--- Calculate close button height
   int buttonsY = promptY + g_promptHeight + g_margin; //--- Calculate buttons y position
   int buttonW = 140;                               //--- Set button width
   int chartX = g_mainContentX + g_sidePadding;     //--- Calculate chart button x position
   int sendX = g_mainContentX + g_mainWidth - g_sidePadding - buttonW; //--- Calculate send button x position
   int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
   int editX = displayX + g_textPadding;            //--- Calculate edit field x position
   bool need_scroll = g_total_height > g_visible_height; //--- Check if main scrollbar is needed
   bool p_need_scroll = p_total_height > p_visible_height; //--- Check if prompt scrollbar is needed
   if (id == CHARTEVENT_OBJECT_CLICK) {             //--- Handle object click event
      if (StringFind(sparam, "ChatGPT_ChatLabel_") == 0) { //--- Check if chat label clicked
         string hashed_id = StringSubstr(sparam, StringLen("ChatGPT_ChatLabel_")); //--- Extract hashed ID
         int new_id = DecodeID(hashed_id);          //--- Decode chat ID
         int idx = GetChatIndex(new_id);            //--- Get chat index
         if (idx >= 0 && new_id != current_chat_id) { //--- Check if valid and different chat
            UpdateCurrentHistory();                 //--- Update current chat history
            current_chat_id = new_id;               //--- Set new chat ID
            current_title = chats[idx].title;       //--- Set new chat title
            conversationHistory = chats[idx].history; //--- Set new conversation history
            UpdateResponseDisplay();                //--- Update response display
            UpdateSidebarDynamic();                 //--- Update sidebar display
            ChartRedraw();                          //--- Redraw chart
         }
         return;                                    //--- Exit function
      }
   }
}

OnChartEventイベントハンドラでは、g_mainContentX、g_sidePadding、g_headerHeight、g_displayHeight、g_promptHeight、g_textPaddingなどの変数を用いて、メイン表示、プロンプト領域、ボタンのレイアウト位置を計算します。また、前バージョンと同様に、g_total_height、g_visible_height、p_total_height、p_visible_heightでスクロールバーの必要性を判定します。

CHARTEVENT_OBJECT_CLICKイベントでは、ChatGPT_ChatLabel_がクリックされたかをStringFindで確認し、ハッシュ化されたIDをStringSubstrで抽出、DecodeIDで復号化します。GetChatIndexを用いて選択されたチャットに切り替え、current_chat_id、current_title、conversationHistoryを更新します。その後、UpdateCurrentHistory、UpdateResponseDisplay、UpdateSidebarDynamicを呼び出し、ChartRedrawでUIを更新することで、サイドバー上でのチャットナビゲーションをシームレスにおこなえるようにします。コンパイルすると、次の結果が得られます。

インタラクションイベントGIF

表示を確認すると、チャートイベントが正常に動作していることがわかります。チャットについては、セッションをまたいでも永続化されており、継続的な応答の取得や送信が可能です。データは暗号化されており、ログにアクセスしても人間が読めない形式になっています。以下はサンプルです。

暗号化されたチャット

表示から、プログラムを新しい要素で拡張し、スクロール可能なプロンプトセクションを表示し、永続的なチャットに対応したインタラクティブなインターフェースを実現できたことが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

バックテストGIF


結論

今回のMQL5プログラムでは、複数行入力の制限を克服する堅牢なテキストレンダリング機能、永続チャットのナビゲーションに対応した動的サイドバー(CRYPT_AES256暗号化およびCRYPT_ARCH_ZIP圧縮付き)、チャートデータ統合による初期売買シグナル生成を追加することで、大幅に機能を強化しました。このシステムにより、AI駆動の市場インサイトとシームレスにやり取りでき、直感的な操作でセッションをまたいだ会話の文脈を維持できます。また、二重スクロールバーを備えた視覚的にブランディングされたUIにより、操作性も向上しています。今後のバージョンでは、AI駆動のシグナル生成をさらに洗練させ、自動取引の実装も検討し、取引アシスタントの能力を一層高める予定です。どうぞご期待ください。


添付ファイル

シリアル番号 名前 種類 説明
1 AI_JSON_FILE.mqh JSONクラスライブラリ JSONのシリアライズおよびデシリアライズを扱うクラス
2 AI_CREATE_OBJECTS_FNS.mqh オブジェクト関数ライブラリ ラベルやボタンなどの可視化オブジェクトを作成する関数
3 AI_ChatGPT_EA_Part_4.mq5 EA AI統合を扱うメインのEA


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19782

添付されたファイル |
AI_JSON_FILE.mqh (26.62 KB)
取引システムの構築(第5回):構造化された取引決済による利益管理 取引システムの構築(第5回):構造化された取引決済による利益管理
利益目標まであとわずかというところで価格が反転し、ストップロスにかかってしまう。トレーリングストップによって建値で決済された直後に、市場が元の方向へ大きく動き、当初の目標を超えていく。多くのトレーダーにとって、これはおなじみの悩みでしょう。本記事では、異なるリスクリワードレシオ(RRR)で複数のエントリーを配置する手法に焦点を当て、利益を体系的に確保しながら、全体のリスク曝露を抑えるアプローチを解説します。
初心者からエキスパートへ:隠れフィボナッチリトレースメントレベルの謎を解く 初心者からエキスパートへ:隠れフィボナッチリトレースメントレベルの謎を解く
本記事では、市場が反応する可能性のある非標準的なフィボナッチリトレースメントレベルを、データ駆動型の手法で発見および検証するアプローチを紹介します。MQL5での実装を想定した完全なワークフローを提示し、データ収集やバーやスイングの検出から始め、クラスタリング、統計的仮説検定、バックテスト、さらにMetaTrader 5のフィボナッチツールへの統合までを包括的にカバーします。ここでの目的は、経験的な観察に基づく推測を、統計的に裏付けられた売買シグナルへと変換する再現可能なパイプラインを構築することです。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
知っておくべきMQL5ウィザードのテクニック(第82回):DQN強化学習でTRIXとWPRのパターンを使用する 知っておくべきMQL5ウィザードのテクニック(第82回):DQN強化学習でTRIXとWPRのパターンを使用する
前回の記事では、推論学習の枠組みにおける一目均衡表とADXの組み合わせを検証しました。本記事では、第68回で最後に取り上げたインジケーターの組み合わせ、すなわちTRIXとWilliams Percent Range (WPR)を対象に、強化学習を再度取り上げます。今回使用するアルゴリズムは、QR-DQN (Quantile Regression DQN)です。これまでと同様に、MQL5ウィザードでの実装を前提としたカスタムシグナルクラスとして提示します。