MQL5でのAI搭載取引システムの構築(第3回):複数行入力の克服、チャットの持続性の確保、シグナル生成
はじめに
前回の記事(第3回)では、MetaQuotes Language 5 (MQL5)で作成したChatGPT統合プログラムに、スクロール可能な単一チャット指向のUIを追加しました。これにより、タイムスタンプ、動的スクロール、マルチターン会話履歴が導入され、MetaTrader 5におけるAIとのインタラクションが大幅に向上しました。第4回では、改良されたテキストレンダリングを用いて複数行入力の制限を克服します。さらに、Advanced Encryption Standard (AES256)暗号化およびZIP圧縮を用いて保存された永続的なチャット履歴をナビゲートするためのサイドバーを追加します。また、AIによる市場分析を可能にするため、チャートデータを統合して初期の売買シグナルを生成します。本記事では以下のトピックを扱います。
この記事を読み終える頃には、操作性が向上し、文脈を考慮した機能を備えたMQL5ベースのAI取引アシスタントを手にしているはずです。それでは始めましょう。
複数行入力処理、サイドバーチャットの永続化、および売買シグナル生成の理解
AI取引システムにおける複数行入力の処理は、複数行の市場説明やコードスニペットなど、詳細なプロンプトやデータを入力できるようにするために不可欠です。これにより、AIが複雑なクエリを切り捨てずに処理でき、単一行入力ではコンテキストが制限される可能性のある動的市場でも正確な応答が可能となります。チャットの永続化は、セッションをまたいで会話履歴を保存することで、同じ情報を繰り返すことなく、過去のAIの分析や洞察を基に作業を進められる点で価値があります。売買シグナル生成では、AIが市場データを分析し、実行可能な買いまたは売りの推奨を生成します。これにより、手動分析の負担を減らし、トレンド転換などのチャンスに迅速に対応可能となります。これらの機能を組み合わせることで、文脈を維持しつつAIをリアルタイム取引判断に統合した堅牢なシステムが構築され、ユーザー体験の向上やエラーの最小化、収益性の改善に繋がります。
本記事では、AIプログラムを拡張し、複数行入力を処理できる高度なテキスト処理を実装します。現状のロジックでは、最大63文字までしか入力できず、単純なプロンプトに制限されていました。これを改良することで、必要に応じて任意の行数を入力できるようにし、AIにより詳細な指示を与えて売買シグナルを生成させることが可能になります。また、チャットの永続化のために、安全な保存メカニズムも組み込みます。これにより、過去の会話を簡単に取得してナビゲートでき、同じ内容を何度も繰り返す必要がなくなります。チャットはAdvanced Encryption Standard (AES)で暗号化し、セキュリティを確保します。AESを選んだのは扱いやすさのためですが、必要に応じて他の方式を使用することも可能です。本記事では暗号化の詳細ロジックには踏み込みませんが、仕組みを示す図を以下に示します。

ここでの考え方は、たとえばXAU/USDのチャートを分析する会話をおこなった後に、GBP/USDに関する別の会話を開始する、といった状況です。後になって過去の応答を確認したり、修正を加えたり、追加のプロンプトをおこないたい場合もあります。その際、会話全体を最初から繰り返すのではなく、保存された履歴を参照できるようにします。
これをより分かりやすくし、進化を確認できるようにするため、AIによる分析に基づいて初期の売買シグナルを生成するためのチャートデータ取得および統合機能を追加します。そのために、インターフェースを再定義し、アイコンやナビゲーションサイドバーを備えた、よりブランディングされたUIを設計します。直感的なナビゲーション要素を持つインターフェースを構築することで、チャット管理やシグナル表示を効率的におこなえるようにし、AIを取引戦略に活用したいユーザーにとって使いやすいシステムを実現します。以下は、最終的に実現する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関数を定義します。この関数は、指定した区切り文字を用いて入力テキストを配列に分割します。StringFindとStringSubstrを使用してセグメントを抽出し、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関数を実装します。これにより、スムーズなレンダリングとスクロールが可能になります。関数はまず、ObjectsTotalとObjectDelete関数を使用して既存の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配列に取得し、TimeToStringやDoubleToStringで始値、最高値、最安値、終値、出来高などの詳細を文字列に整形します。
取得したデータはログに出力し、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で分割し、各行をStringTrimLeftとStringTrimRightで前後の空白を削除します。空行や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を更新することで、サイドバー上でのチャットナビゲーションをシームレスにおこなえるようにします。コンパイルすると、次の結果が得られます。

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

表示から、プログラムを新しい要素で拡張し、スクロール可能なプロンプトセクションを表示し、永続的なチャットに対応したインタラクティブなインターフェースを実現できたことが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
取引システムの構築(第5回):構造化された取引決済による利益管理
初心者からエキスパートへ:隠れフィボナッチリトレースメントレベルの謎を解く
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
知っておくべきMQL5ウィザードのテクニック(第82回):DQN強化学習でTRIXとWPRのパターンを使用する
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索