English Русский Deutsch
preview
MQL5取引ツール(第14回):アンチエイリアシングと角丸スクロールバーを備えたピクセルパーフェクトなスクロール対応テキストキャンバス

MQL5取引ツール(第14回):アンチエイリアシングと角丸スクロールバーを備えたピクセルパーフェクトなスクロール対応テキストキャンバス

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

はじめに

前回の記事(第13回)では、MetaQuotes Language 5 (MQL5)のCCanvasクラスを使用して、価格グラフを可視化するCanvasベースの価格ダッシュボードを作成しました。このダッシュボードには、ラインプロットやフォグ効果を用いた価格表示機能に加え、口座情報やバー情報を表示する統計パネルを実装しました。また、背景画像やグラデーション、テーマ切り替え、ドラッグ移動、サイズ変更機能などをイベント処理とともにサポートしました。第14回では、MQL5の制限を回避するために、アンチエイリアス処理を施したピクセルパーフェクトなスクロール可能テキストキャンバスを追加し、さらに改良を加えています。この機能強化では、使用ガイド用のテキストパネルを導入し、カスタムのアンチエイリアス処理により、滑らかなエッジ表現を実現しています。ホバー時に拡張されるスクロールバーを備えており、ボタンやスライダーによる操作、ホイール入力への対応、テーマ付きの背景や透明度設定、動的な折り返し表示や色付きテキスト表示を含み、包括的なユーザー向け説明をシームレスに統合しています。本記事では以下のトピックを扱います。

  1. ピクセルパーフェクトなスクロール可能テキストキャンバスフレームワークの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的には、詳細でインタラクティブなテキストガイドパネルを備えたMQL5キャンバスダッシュボードが完成し、さらなるカスタマイズにも対応できるようになります。それでは、さっそく始めましょう。


ピクセルパーフェクトなスクロール可能テキストキャンバスフレームワークの理解

ピクセルパーフェクトなスクロール可能テキストキャンバスフレームワークは、MQL5のネイティブなテキストスクロールの制約を解決するために設計されています。この仕組みでは、滑らかなエッジを実現するアンチエイリアス付きのピクセルレベル描画を採用し、さらにホバー時に拡張される丸みのあるスクロールバーを用いることで、操作性を向上させています。また、上下ボタンやドラッグ可能なスライダーといったインタラクティブ要素を備え、長いコンテンツや使用ガイドの閲覧を直感的におこなえるようになっています。さらに、テーマ対応の背景と透明度調整機能をサポートし、パネル幅に応じた動的な行の折り返しを実現しながら、見出しやリンクの色情報を保持する設計となっています。マウスホイールによるスクロールにも対応しており、チャートのズーム操作と干渉しない形でテキスト領域内のスクロール制御を可能にしています。これにより、組み込みオブジェクトに依存せず、より精密な操作が実現されています。ダッシュボードとの統合によってイベントに応じたリアルタイム更新も可能となり、グラフ、統計、テキストパネル間で一体化されたモニタリングツールとして機能します。

本設計では、静的なMQL5オブジェクトによるライン描画は採用せず、キャンバスの機能を最大限活用する方針を取っています。これにより、過去の記事で発生していたようなテキストが領域外にはみ出す問題を回避でき、キャンバス側で自動的にクリッピング処理がおこなわれるため、Webページのようなスクロール体験を実現できます。また、丸みのあるダイナミックスクロールバーは、MetaQuotesの最新アップデートに見られるターミナルUIの洗練されたオーバーレイデザインから着想を得ています。以下は、スクロールバー設計の参考としたMetaQuotesのUIです。

METAQUOTESスクロールバーのインスピレーション

今後の拡張として、テキストパネルの高さや透明度などの入力と列挙オプションの追加、テキストキャンバスオブジェクトの生成と破棄ロジックの実装、スクロール状態、位置、寸法、色に関するグローバル変数の定義、色検出や見出しの強調表示を伴うテキスト折り返し機能の実装を予定しています。さらに、円弧、円、矩形を用いたホバー効果付きの角丸スクロールバー(ボタンとスライダー含む)の描画、また、テキスト領域内でのホバー、クリック、ドラッグ、ホイール操作を処理し、スクロール位置やツールチップ表示の更新、必要に応じたスクロール無効化にも対応します。加えて、テーマ切り替えや最小化、サイズ変更操作に応じてテキストパネルが動的に調整される仕組みも組み込みます。以下に想定されるビジュアル表示の例を示します。

テキストキャンバス目標フレームワーク


MQL5での実装

MQL5でプログラムを拡張するには、新しいテキストCanvasパネルを宣言し、レンダリングを動的に制御するための追加の入力とグローバル変数を追加する必要があります。

//+------------------------------------------------------------------+
//|                                       Canvas Dashboard PART2.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

//+------------------------------------------------------------------+
//| Canvas objects                                                   |
//+------------------------------------------------------------------+

//---

CCanvas canvasText;                  // Declare text canvas

//+------------------------------------------------------------------+
//| Canvas names                                                     |
//+------------------------------------------------------------------+

//---

string canvasTextName = "TextCanvas";   // Set text canvas name

//--- Added Extra inputs

input bool EnableTextPanel            = true; // Enable Text Panel
input int TextPanelHeight             = 200; // Text Panel Height
input double TextBackgroundOpacityPercent = 85.0; // Text Background Opacity Percent

input double StatsHeaderBgOpacityPercent = 20.0; // Stats Header Bg Opacity Percent
input int StatsHeaderBgRadius         = 8; // Stats Header Bg Radius

//--- Extended the globals

uint bg_pixels_text[];               //--- Store text background pixels

int prev_mouse_state = 0;            //--- Initialize previous mouse state
int last_mouse_x = 0, last_mouse_y = 0; //--- Initialize last mouse positions
int header_height = 27;              //--- Set header height
int gap_y = 7;                       //--- Set Y gap
int button_size = 25;                //--- Set button size
int theme_x_offset = -75;            //--- Set theme X offset
int minimize_x_offset = -50;         //--- Set minimize X offset
int close_x_offset = -25;            //--- Set close X offset
bool panels_minimized = false;       //--- Initialize panels minimized flag
color HeaderColor = C'60,60,60';     //--- Set header color
color HeaderHoverColor = clrRed;     //--- Set header hover color
color HeaderDragColor = clrMediumBlue; //--- Set header drag color

初めにおこなう拡張の実装として、新しいテキストパネル用に追加のCCanvasオブジェクトcanvasTextを宣言します。このオブジェクトはスクロール可能な利用ガイドを表示するために使用します。また、各キャンバスを一意に識別するために、文字列定数canvasTextNameを追加し、値をTextCanvasに設定します。これは他のキャンバスと同様の設計です。次に、テキストパネルをサポートするために入力パラメータを拡張します。EnableTextPanelはbool型でデフォルトtrueとし、テキストパネルの表示と非表示を切り替えるために使用します。TextPanelHeightは200に設定し、パネルの高さを制御します。TextBackgroundOpacityPercentは85.0とし、背景の透明度を調整します。StatsHeaderBgOpacityPercentは20.0とし、統計ヘッダー背景の透過率を制御します。StatsHeaderBgRadiusは8とし、ヘッダーの角丸半径を定義します。また、グラフや統計パネルと背景表現を統一するためにuint配列bg_pixels_textを追加し、テキストパネル用にスケーリングされた背景ピクセルデータを格納します。

次に、マウス操作に関するグローバル変数を定義および初期化します。prev_mouse_stateは0、last_mouse_xとlast_mouse_yもそれぞれ0で初期化し、マウスの移動追跡に使用します。header_heightは27、gap_yは7とし、レイアウト上の垂直間隔を定義します。button_sizeは25とします。ヘッダー右側に配置するボタンのオフセットとしてtheme_x_offsetは-75、minimize_x_offsetは-50、close_x_offsetは-25とします。またpanels_minimizedはfalseで初期状態は展開状態とします。HeaderColorを中間グレー、HeaderHoverColorを赤、HeaderDragColorをミディアムブルーとし、それぞれヘッダーの状態に応じて使用します。次のステップでは、テキストパネルに表示するコンテンツの定義と、それを制御するための追加グローバル変数を実装します。

string text_usage_text = 
"\nCanvas Dashboard Usage Guide\n\n"
"Welcome to the Canvas Dashboard – Your Interactive Tool for Real-Time Market Monitoring in MetaTrader 5!\n\n"
"Enhance your trading experience with this dynamic dashboard that visualizes price data, account stats, and interactive controls. Designed for ease of use, it allows customization through dragging, resizing, theme switching, and more, while providing essential market insights at a glance.\n\n"
"Key Features:\n"
"- Price Graph Panel: Displays recent bar closes with a fog gradient fill, background image support, and resize indicators.\n"
"- Stats Panel: Shows account balance, equity, and current bar OHLC values with customizable backgrounds (single color or gradient).\n"
"- Header Controls: Drag to move the dashboard; buttons for theme toggle (dark/light), minimize/maximize panels, and close.\n"
"- Text Panel: Scrollable guide (this panel) with hover-expand scrollbar, up/down buttons, and slider for navigation.\n"
"- Interactivity: Hover for highlights/tooltips; resize via borders (bottom, right, corner); wheel scroll in text area.\n"
"- Theme Support: Switch between dark and light modes for better visibility on different chart backgrounds.\n"
"- Background Options: Enable images with fog overlay; blend modes for transparency.\n\n"
"Usage Instructions:\n"
"1. Move the Dashboard: Click and drag the header (excluding buttons) to reposition on the chart.\n"
"2. Resize Panels: Hover near the graph's bottom/right edges; click and drag when the icon appears (arrows for direction).\n"
"3. Toggle Theme: Click the '[' icon in the header to switch between dark and light modes.\n"
"4. Minimize/Maximize: Click the 'r' or 'o' icon to hide/show panels (header remains visible).\n"
"5. Navigate Text: Use the scrollbar (hovers to expand with buttons/slider); click up/down or drag slider; wheel scroll in body.\n"
"6. Close Dashboard: Click the 'X' icon in the header to remove it from the chart.\n\n"
"Important Notes:\n"
"- Risk Disclaimer: This dashboard is for informational purposes only. Always verify data and trade responsibly.\n"
"- Compatibility Check: Ensure chart settings allow mouse events; test on demo for interactions.\n"
"- Optimization Tips: Adjust input parameters like graph bars, fonts, colors, and opacity for your setup.\n"
"- Security Measures: No account modifications; use on trusted platforms.\n"
"- Legal Notice: No guarantees of accuracy. Consult professionals as needed.\n\n"
"Contact Methods:\n"
"NB:\n"
"********************************************\n"
" >*** FOR SUPPORT, QUERIES, OR CUSTOMIZATIONS, REACH OUT IMMEDIATELY: ***<\n"
" __________________________________________\n\n"
"1. Email: mutiiriallan.forex@gmail.com (Primary Support Channel)\n"
"2. Telegram Channel: @ForexAlgo-Trader (Updates & Community)\n"
"3. Telegram Group: https://t.me/Forex_Algo_Trader (Direct Assistance & Discussions)\n\n"
"********************************************\n\n"
"Thank you for choosing our Canvas Dashboard solutions. Use wisely, monitor confidently, and elevate your trading journey! 🚀\n"; //--- Set text usage content

int text_scroll_pos = 0;                   //--- Initialize text scroll position
int text_max_scroll = 0;                   //--- Initialize text max scroll
int text_slider_height = 20;               //--- Set text slider height
bool text_movingStateSlider = false;       //--- Initialize text slider moving flag
int text_mlbDownY_Slider = 0;              //--- Initialize text slider down Y
int text_mlbDown_YD_Slider = 0;            //--- Initialize text slider down YD
int text_total_height = 0;                 //--- Initialize text total height
int text_visible_height = 0;               //--- Initialize text visible height
bool text_scroll_visible = false;          //--- Initialize text scroll visible flag
bool text_mouse_in_body = false;           //--- Initialize text mouse in body flag
bool prev_text_mouse_in_body = false;      //--- Initialize previous text mouse in body flag
bool text_scroll_up_hovered = false;       //--- Initialize text scroll up hovered flag
bool text_scroll_down_hovered = false;     //--- Initialize text scroll down hovered flag
bool text_scroll_slider_hovered = false;   //--- Initialize text scroll slider hovered flag
bool text_scroll_area_hovered = false;     //--- Initialize text scroll area hovered flag
const int TEXT_MAX_LINES = 100;            //--- Set text max lines
int text_adjustedLineHeight = 0;           //--- Initialize text adjusted line height

int text_scrollbar_full_width = 16;        //--- Set text scrollbar full width
int text_scrollbar_thin_width = 2;         //--- Set text scrollbar thin width
int text_track_width = 16;                 //--- Set text track width
int text_scrollbar_margin = 5;             //--- Set text scrollbar margin
int text_button_size = 16;                 //--- Set text button size
color text_leader_color_dark = C'45,45,45'; //--- Set dark text leader color
color text_leader_color_light = C'200,200,200'; //--- Set light text leader color
color text_button_bg_dark = C'60,60,60';        //--- Set dark text button bg
color text_button_bg_light = C'220,220,220';    //--- Set light text button bg
color text_button_bg_hover_dark = C'70,70,70';  //--- Set dark text button bg hover
color text_button_bg_hover_light = C'180,180,180'; //--- Set light text button bg hover
color text_arrow_color_dark = C'150,150,150';   //--- Set dark text arrow color
color text_arrow_color_light = C'50,50,50';     //--- Set light text arrow color
color text_arrow_color_disabled_dark = C'80,80,80'; //--- Set dark disabled arrow color
color text_arrow_color_disabled_light = C'150,150,150'; //--- Set light disabled arrow color
color text_arrow_color_hover_dark = C'100,100,100'; //--- Set dark hover arrow color
color text_arrow_color_hover_light = C'0,0,0';  //--- Set light hover arrow color
color text_slider_bg_dark = C'80,80,80';        //--- Set dark slider bg
color text_slider_bg_light = C'150,150,150';    //--- Set light slider bg
color text_slider_bg_hover_dark = C'100,100,100';  //--- Set dark slider bg hover
color text_slider_bg_hover_light = C'100,100,100'; //--- Set light slider bg hover

color text_bg_light = clrWhite;                 //--- Set light text bg
color text_bg_dark = clrBlack;                  //--- Set dark text bg
color text_base_light = clrBlack;               //--- Set light text base
color text_base_dark = clrWhite;                //--- Set dark text base

ここでは、文字列text_usage_textを定義し、テキストパネルに表示される使用ガイドの全コンテンツを格納します。このコンテンツは改行文字によって段落やセクションごとに構造化されており、ウェルカムメッセージ、パネル、コントロール、インタラクティビティ、テーマ、背景に関する主要機能の箇条書き、移動、サイズ変更、表示切替、ナビゲーション、クローズなどの使用手順を番号付きで整理したステップ、リスク、互換性、最適化、セキュリティ、法的事項を含む重要な注意事項、メールアドレスおよびTelegramの連絡先情報、そして最後のサンクスメッセージで構成されます。この構成により、ユーザーガイドとして必要な情報を一通り提供できます。あくまで一例のフォーマットとして必要に応じて自由に変更可能です。

次に、テキストスクロール管理用の変数を初期化します。text_scroll_posは現在のスクロール位置を保持するために0で初期化されます。text_max_scrollはスクロール可能な最大値を保持し、同様に0で初期化されます。text_slider_heightはスライダーサイズとして20に設定されます。text_movingStateSliderはスライダーのドラッグ状態を管理するフラグでありfalseで初期化されます。text_mlbDownY_Sliderおよびtext_mlbDown_YD_Sliderはスライダー操作時のマウス押下Y座標を保持するために0で初期化されます。text_total_heightとtext_visible_heightはそれぞれ全コンテンツ高さと表示領域高さを表し、初期値は0です。text_scroll_visibleはスクロールバーの表示制御フラグとしてfalseで初期化されます。text_mouse_in_bodyとprev_text_mouse_in_bodyはマウスがテキスト領域内にあるかどうかを追跡するためのフラグであり、どちらもfalseで初期化されます。さらに、text_scroll_up_hovered、text_scroll_down_hovered、text_scroll_slider_hovered、text_scroll_area_hoveredといったホバー状態フラグもすべてfalseで初期化されます。TEXT_MAX_LINESは100に設定し、描画ラインの上限バッファとして使用されます。またtext_adjustedLineHeightは行間を動的に調整するための値として0で初期化されます。次にスクロールバーの寸法を定義します。text_scrollbar_full_widthは展開時の幅として16、text_scrollbar_thin_widthは縮小時の幅として2に設定されます。text_track_widthはトラック幅として16、text_scrollbar_marginは余白として5、text_button_sizeは上下ボタンサイズとして16に設定されます。テーマカラーはC'...'形式で定義され、スクロールバー背景、ボタンの通常、ホバー状態、矢印の通常、無効、ホバー状態などを含みます。ダークモードとライトモードの両方に対応し、視認性と一貫性を確保する設計です。ベース背景色およびテキスト色もテーマに応じて白黒を切り替えることでコントラストを維持します。

次のステップでは、統計キャンバスに角丸矩形を描画する機能を追加するための関数を実装します。

//+------------------------------------------------------------------+
//| Fill rounded rectangle                                           |
//+------------------------------------------------------------------+
void FillRoundedRectangle(CCanvas &cvs, int x, int y, int w, int h, int radius, uint argb_color)
  {
   if (radius <= 0) {
      cvs.FillRectangle(x, y, x + w - 1, y + h - 1, argb_color);                //--- Fill rectangle if no radius
      return;                          //--- Exit
     }
   radius = MathMin(radius, MathMin(w / 2, h / 2));                             //--- Adjust radius

   cvs.Arc(x + radius, y + radius, radius, radius, DegreesToRadians(180), DegreesToRadians(90), argb_color); //--- Draw top-left arc
   cvs.Arc(x + w - radius - 1, y + radius, radius, radius, DegreesToRadians(270), DegreesToRadians(90), argb_color); //--- Draw top-right arc
   cvs.Arc(x + w - radius - 1, y + h - radius - 1, radius, radius, DegreesToRadians(0), DegreesToRadians(90), argb_color); //--- Draw bottom-right arc
   cvs.Arc(x + radius, y + h - radius - 1, radius, radius, DegreesToRadians(90), DegreesToRadians(90), argb_color); //--- Draw bottom-left arc

   cvs.FillCircle(x + radius, y + radius, radius, argb_color);                  //--- Fill top-left circle
   cvs.FillCircle(x + w - radius - 1, y + radius, radius, argb_color);          //--- Fill top-right circle
   cvs.FillCircle(x + w - radius - 1, y + h - radius - 1, radius, argb_color);  //--- Fill bottom-right circle
   cvs.FillCircle(x + radius, y + h - radius - 1, radius, argb_color);          //--- Fill bottom-left circle

   cvs.FillRectangle(x + radius, y, x + w - radius - 1, y + h - 1, argb_color); //--- Fill horizontal areas

   cvs.FillRectangle(x, y + radius, x + w - 1, y + h - radius - 1, argb_color); //--- Fill vertical areas
  }

FillRoundedRectangle関数を実装し、指定したCCanvas参照オブジェクト上に角丸矩形を描画します。この関数は、xおよびyの位置、幅wと高さh、角丸半径radius、ARGBカラーcolorをパラメータとして受け取ります。まず、radiusが0以下の場合は通常の矩形描画として処理し、(x,y)から(x+w-1, y+h-1)までをFillRectangleメソッドで塗りつぶした後、そこで処理を終了します。radiusが有効な場合は、まずMathMinを用いてradiusをw/2およびh/2の最小値に制限し、形状が破綻しないように調整します。次に各角に対して円弧を描画します。Arc関数を用いて四隅それぞれの90度セグメントを描きます。具体的には、左上は180度から270度、右上は270度から360度、右下は0度から90度、左下は90度から180度の範囲を描画します。これらの角度はDegreesToRadiansのようなヘルパー関数を使用してラジアンへ変換して使用します。このヘルパー関数は後ほど定義します。

次に、各角の中心位置でFillCircleを使用して完全な円を塗りつぶし、角丸領域を固めます。次に、横方向の中央ストリップを左側のradiusから右側の(w - radius)までの範囲で高さ全体にわたり塗りつぶし、縦方向の側面については上側のradiusから下側の(h - radius)までの範囲を幅全体にわたりFillRectangleで塗りつぶし、隙間なく形状を完成させます。補助的な変換関数は以下の通りです。

//+------------------------------------------------------------------+
//| Convert degrees to radians                                       |
//+------------------------------------------------------------------+
double DegreesToRadians(double degrees) {
   return((M_PI * degrees) / 180.0); //--- Perform conversion
}

πまたはM_PIと対象の角度の乗算結果を180で割ることで計算をおこないます。これにより角度をラジアンへ変換できます。次に、この角丸矩形関数を使用して統計パネル内のヘッダーを描画するため、該当関数を更新します。

//+------------------------------------------------------------------+
//| Update stats on canvas                                           |
//+------------------------------------------------------------------+
void UpdateStatsOnCanvas()
  {

//---

string headerText = "Account Stats";                               //--- Set header text
canvasStats.FontSet("Arial Bold", StatsHeaderFontSize);            //--- Set font
int textW = canvasStats.TextWidth(headerText);                     //--- Get text width
int textH = canvasStats.TextHeight(headerText);                    //--- Get text height
int pad = 5;                                                       //--- Set padding
int rectX = (statsWidth - textW) / 2 - pad;                        //--- Calculate rect X
int rectY = yPos - pad / 2;                                        //--- Calculate rect Y
int rectW = textW + 2 * pad;                                       //--- Calculate rect width
int rectH = textH + pad;                                           //--- Calculate rect height
uchar alpha = (uchar)(255 * (StatsHeaderBgOpacityPercent / 100.0)); //--- Calculate alpha
uint argbHeaderBg = ColorToARGB(GetStatsHeaderBgColor(), alpha);   //--- Convert bg to ARGB

color header_border = is_dark_theme ? clrWhite : clrBlack;         //--- Get border color
uint argbHeaderBorder = ColorToARGB(header_border, 255);           //--- Convert to ARGB
FillRoundedRectangle(canvasStats, rectX - 1, rectY - 1, rectW + 2, rectH + 2, StatsHeaderBgRadius + 1, argbHeaderBorder); //--- Fill outer rectangle

FillRoundedRectangle(canvasStats, rectX, rectY, rectW, rectH, StatsHeaderBgRadius, argbHeaderBg); //--- Fill inner rectangle

uint argbHeader = ColorToARGB(headerColor, 255);                   //--- Convert header to ARGB
canvasStats.TextOut(statsWidth / 2, yPos, headerText, argbHeader, TA_CENTER); //--- Draw header

//---

   }

変更を実装するために、UpdateStatsOnCanvas関数を修正し、ヘッダー描画を強化して角丸背景を追加することで、より洗練された外観にします。各ヘッダー(たとえばAccount StatsやCurrent Bar Stats)ごとに、フォントをArial Boldに設定し、StatsHeaderFontSizeをFontSetで指定します。次にTextWidthTextHeightを使用してテキストの幅と高さを計測し、パディングを5ピクセルに設定します。そのうえで、パディングを含めた中央配置の矩形位置とサイズを計算します。xは(width - text width) / 2 - pad、yは現在のyPos - pad / 2、幅はtext + pad * 2、高さはtext + padとなります。

次に、StatsHeaderBgOpacityPercentを100.0で割った値に255を掛けてucharにキャストし、アルファ値を計算します。背景色はGetStatsHeaderBgColorから取得し、このアルファ値を用いてARGBへ変換します。枠線についてはダークテーマの場合は白、ライトテーマの場合は黒を選択し、完全不透明のARGB値に変換します。その後FillRoundedRectangleを2回呼び出します。1回目は外枠として位置を-1、サイズを+2、半径を+1として枠線ARGBで描画し、2回目はベース位置、サイズ、半径で背景ARGBを用いて描画します。ヘッダー色をアルファ255のARGB形式に変換し、stats幅を2で割った位置とyPosを基準としてテキストを中央揃えでTextOutを使用して描画します。その後、次のセクションとの間隔としてyPosを30増加させます。関数の残りの部分は以前定義した通りです。コンパイル後、次のような結果になります。

統計見出しを角丸長方形に変更した

これにより、統計パネルのヘッダーが角丸矩形として描画されるようになります。次に実装するのはテキストパネルの描画です。他のパネルと同様に専用関数を作成しますが、その前にテキストを動的に折り返す必要があります。そのために、後で使用する補助関数群と併せてテキストラッピングを実装します。そのために、以下の手法を使用します。

//+------------------------------------------------------------------+
//| Lighten color                                                    |
//+------------------------------------------------------------------+
color LightenColor(color colorValue, double factor)
  {
   int blue = (int)MathMin(255, (colorValue & 0xFF) * factor);         //--- Lighten blue
   int green = (int)MathMin(255, ((colorValue >> 8) & 0xFF) * factor); //--- Lighten green
   int red = (int)MathMin(255, ((colorValue >> 16) & 0xFF) * factor);  //--- Lighten red
   return (color)(blue | (green << 8) | (red << 16));                  //--- Return lightened color
  }

//+------------------------------------------------------------------+
//| Calculate slider height                                          |
//+------------------------------------------------------------------+
int TextCalculateSliderHeight()
  {
   int scroll_area_height = TextPanelHeight - 2 - 2 * text_button_size; //--- Calculate area height
   int slider_min_height = 20;         //--- Set min height
   if (text_total_height <= text_visible_height) return scroll_area_height; //--- Return full if no scroll
   double visible_ratio = (double)text_visible_height / text_total_height;  //--- Calculate ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio);         //--- Calculate height
   return MathMax(slider_min_height, height);                           //--- Return max of min and calculated
  }

//+------------------------------------------------------------------+
//| Scroll text up                                                   |
//+------------------------------------------------------------------+
void TextScrollUp()
  {
   if (text_adjustedLineHeight > 0 && text_scroll_pos > 0)
     {
      text_scroll_pos = MathMax(0, text_scroll_pos - text_adjustedLineHeight); //--- Scroll up
      UpdateTextOnCanvas();                                            //--- Update canvas
     }
  }

//+------------------------------------------------------------------+
//| Scroll text down                                                 |
//+------------------------------------------------------------------+
void TextScrollDown()
  {
   if (text_adjustedLineHeight > 0 && text_scroll_pos < text_max_scroll)
     {
      text_scroll_pos = MathMin(text_max_scroll, text_scroll_pos + text_adjustedLineHeight); //--- Scroll down
      UpdateTextOnCanvas();                                            //--- Update canvas
     }
  }

//+------------------------------------------------------------------+
//| Update text hover effects                                        |
//+------------------------------------------------------------------+
void TextUpdateHoverEffects(int local_x, int local_y)
  {
   if (!text_scroll_visible) return;                                    //--- Exit if no scroll
   int scrollbar_x = canvasText.Width() - text_track_width - 1;         //--- Get scrollbar X
   int scrollbar_y = 1;                                                 //--- Set scrollbar Y
   int scrollbar_height = TextPanelHeight - 2;                          //--- Get height
   text_scroll_up_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                             local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1); //--- Check up hover
   int down_y = scrollbar_y + scrollbar_height - text_button_size;      //--- Calculate down Y
   text_scroll_down_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                               local_y >= down_y && local_y <= down_y + text_button_size - 1); //--- Check down hover
   int scroll_area_height = scrollbar_height - 2 * text_button_size;    //--- Calculate area height
   int slider_y = scrollbar_y + text_button_size + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
   text_scroll_slider_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                                 local_y >= slider_y && local_y <= slider_y + text_slider_height - 1); //--- Check slider hover
  }

//+------------------------------------------------------------------+
//| Get line color                                                   |
//+------------------------------------------------------------------+
color GetLineColor(string lineText)
  {
   if (StringLen(lineText) == 0 || lineText == " ") return C'25,25,25'; //--- Return gray for empty
   if (StringFind(lineText, "mutiiriallan.forex@gmail.com") >= 0) return C'255,100,100';   //--- Return red for email
   if (StringFind(lineText, "https://t.me/Forex_Algo_Trader") >= 0) return C'150,100,200'; //--- Return purple for link
   if (StringFind(lineText, "@ForexAlgo-Trader") >= 0) return C'100,150,255';              //--- Return blue for channel
   if (StringFind(lineText, "http") >= 0 || StringFind(lineText, "t.me") >= 0) return C'100,150,255'; //--- Return blue for links
   string start3 = StringSubstr(lineText, 0, 3);                        //--- Get start substring
   if ((start3 == "1. " || start3 == "2. " || start3 == "3. " || start3 == "4. " || start3 == "5. ") &&
       StringFind(lineText, "Initial Setup Instructions") < 0) return C'255,200,100';      //--- Return orange for numbered
   return clrWhite;                                                     //--- Return white default
  }

//+------------------------------------------------------------------+
//| Check if heading                                                 |
//+------------------------------------------------------------------+
bool IsHeading(string lineText)
  {
   if (StringLen(lineText) == 0) return false;                          //--- False for empty
   if (StringGetCharacter(lineText, StringLen(lineText) - 1) == ':') return true; //--- True for colon end
   if (StringFind(lineText, "Canvas Dashboard Usage Guide") >= 0) return true;    //--- True for guide
   if (StringFind(lineText, "Key Features") >= 0) return true;          //--- True for features
   if (StringFind(lineText, "Usage Instructions") >= 0) return true;    //--- True for instructions
   if (StringFind(lineText, "Important Notes") >= 0) return true;       //--- True for notes
   if (StringFind(lineText, "Contact Methods") >= 0) return true;       //--- True for contacts
   if (StringFind(lineText, "NB:") >= 0) return true;                   //--- True for NB
   return false;                                                        //--- Return false
  }

//+------------------------------------------------------------------+
//| Wrap text                                                        |
//+------------------------------------------------------------------+
void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], color &wrappedColors[])
  {
   ArrayResize(wrappedLines, 0);                                        //--- Reset lines
   ArrayResize(wrappedColors, 0);                                       //--- Reset colors
   string paragraphs[];                                                 //--- Declare paragraphs
   int numParagraphs = StringSplit(inputText, '\n', paragraphs);        //--- Split by newline
   for (int p = 0; p < numParagraphs; p++)
     {
      string para = paragraphs[p];                                      //--- Get paragraph
      color paraColor = GetLineColor(para);                             //--- Get color
      if (StringLen(para) == 0)
        {
         int size = ArraySize(wrappedLines);                            //--- Get size
         ArrayResize(wrappedLines, size + 1);                           //--- Resize
         wrappedLines[size] = " ";                                      //--- Add space
         ArrayResize(wrappedColors, size + 1);                          //--- Resize colors
         wrappedColors[size] = C'25,25,25';                             //--- Set gray
         continue;                                                      //--- Continue
        }
      string words[];                                                   //--- Declare words
      int numWords = StringSplit(para, ' ', words);                     //--- Split by space
      string currentLine = "";                                          //--- Initialize line
      for (int w = 0; w < numWords; w++)
        {
         string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line
         canvasText.FontSet(font, fontSize);                            //--- Set font
         int textW = canvasText.TextWidth(testLine);                    //--- Get width
         if (textW <= maxWidth)
           {
            currentLine = testLine;                                     //--- Update line
           }
         else
           {
            if (StringLen(currentLine) > 0)
              {
               int size = ArraySize(wrappedLines);                     //--- Get size
               ArrayResize(wrappedLines, size + 1);                    //--- Resize
               wrappedLines[size] = currentLine;                       //--- Add line
               ArrayResize(wrappedColors, size + 1);                   //--- Resize colors
               wrappedColors[size] = paraColor;                        //--- Set color
              }
            currentLine = words[w];                                    //--- Start new line
           }
        }
      if (StringLen(currentLine) > 0)
        {
         int size = ArraySize(wrappedLines);                           //--- Get size
         ArrayResize(wrappedLines, size + 1);                          //--- Resize
         wrappedLines[size] = currentLine;                             //--- Add last line
         ArrayResize(wrappedColors, size + 1);                         //--- Resize colors
         wrappedColors[size] = paraColor;                              //--- Set color
        }
     }
  }

いくつかのヘルパー関数を定義します。まず、LightenColor関数を実装します。この関数はcolorValueとdouble型のfactorを受け取り、RGB成分を抽出したうえでfactorを掛けることで色を明るくします。各成分はMathMinを用いて255を上限としてクランプし、オーバーフローを防ぎます。これはダークモードにおけるテキストカラーなど、テーマ調整用途で使用されます。colorValueとdouble型の係数を受け取り、ビットマスクとシフト演算を用いて青、緑、赤の各成分を抽出します。各成分に係数を掛け、その結果をintにキャストしたうえでMathMinを使用して255を上限として制限します。その後、各成分をビットシフトとOR演算で結合し、最終的なcolor型として再構成します。

次に、TextCalculateSliderHeight関数を作成します。この関数は、表示領域に対する全体コンテンツ量の比率に基づいてスクロールバーのスライダーサイズを計算します。最低サイズを保証することで操作性を維持します。まずtext_button_sizeを用いて上下ボタンを含めた領域からTextPanelHeightを引き、実際のスライダー可能領域を算出します。最小値は20とし、スクロールが不要な場合はスライダーサイズを全領域として返します。スクロールが必要な場合はvisibleとtotalの比率に応じてarea * visible/totalを計算し、最小値と比較して大きい方を返します。次にTextScrollUpを定義します。この関数はtext_scroll_posをtext_adjustedLineHeight分だけ減少させることで上方向へスクロールします。ただし0未満にならないよう制限し、更新後はUpdateTextOnCanvasを呼び出して再描画します。同様にTextScrollDownはtext_scroll_posを1行分増加させ、text_max_scrollを超えないよう制限したうえでUpdateTextOnCanvasを呼び出します。

TextUpdateHoverEffects関数では、スクロールバー各部分のホバー状態を検出します。スクロールバーが非表示の場合は即時終了します。スクロールバーの位置とサイズを計算し、テキスト領域内であることを条件として、上ボタン領域ではtext_scroll_up_hovered、下ボタン領域ではtext_scroll_down_hovered、スライダー領域ではtext_scroll_slider_hoveredを更新します。またtext_scroll_area_hoveredも併せて判定に使用します。GetLineColor関数では行内容に応じた色分類をおこないます。空行またはスペースのみの場合は灰色、emailアドレスは赤、特定リンクやチャンネルやhttp、t.meを含む場合は紫または青、番号付きリストは橙、それ以外の特定パターンに該当しない場合は白をデフォルトとします。IsHeading関数では見出し判定をおこないます。空行はfalse、末尾がコロンの場合やguide、features、instructions、notes、contacts、NBなどの特定キーワードに一致する場合はtrueとします。なお、この識別は必要に応じてHTML的な形式を用いることも可能ですが、ここでは簡易的なルールベース判定を採用しています。様々なアイデアがあるので、自分に合ったものを選んでください。

最後に、WrapTextは入力テキストを幅制約に基づいて複数行へ分割し、それぞれの行に対応する色情報を付与します。出力用の配列を初期化し、まず改行でテキストを段落に分割します。各段落ごとに色を取得し、空行の場合はスペースを追加して灰色として扱います。次に、各段落を単語単位に分割し、行を構築します。新しい単語を追加した場合のテスト行を作成し、テンポラリフォントを設定した状態でTextWidthを用いて幅を測定します。収まる場合は現在の行に追加し、収まらない場合は現在の行を確定して出力配列に追加し、新しい行として開始します。すべての単語処理後、残っている行がある場合はそれを出力配列に追加します。これらのヘルパー関数により、WrapTextはテキストを幅制約に従って分割し、色情報を保持したまま行データへ変換します。

//+------------------------------------------------------------------+
//| Update text on canvas                                            |
//+------------------------------------------------------------------+
void UpdateTextOnCanvas()
  {
   canvasText.Erase(0);                //--- Clear text canvas
   
   int textWidth = canvasText.Width(); //--- Get text width
   int textHeight = TextPanelHeight;   //--- Get text height
   
   color text_bg = is_dark_theme ? text_bg_dark : text_bg_light;                  //--- Get bg color
   uint argb_bg = ColorToARGB(text_bg, (uchar)(255 * (TextBackgroundOpacityPercent / 100.0))); //--- Convert to ARGB
   canvasText.FillRectangle(0, 0, textWidth - 1, textHeight - 1, argb_bg);        //--- Fill background
   
   uint argbBorder = ColorToARGB(GetBorderColor(), 255);                          //--- Convert border
   canvasText.Line(0, 0, textWidth - 1, 0, argbBorder);                           //--- Draw top
   canvasText.Line(textWidth - 1, 0, textWidth - 1, textHeight - 1, argbBorder);  //--- Draw right
   canvasText.Line(textWidth - 1, textHeight - 1, 0, textHeight - 1, argbBorder); //--- Draw bottom
   canvasText.Line(0, textHeight - 1, 0, 0, argbBorder); //--- Draw left
   
   int padding = 10;                   //--- Set padding
   int textAreaX = 1 + padding;        //--- Calculate area X
   int textAreaY = 1;                  //--- Set area Y
   int textAreaWidth = textWidth - 2 - padding * 2; //--- Calculate area width
   int textAreaHeight = textHeight - 2; //--- Calculate area height
   string font = "Arial";              //--- Set font
   int fontSize = 16;                  //--- Set font size
   canvasText.FontSet(font, fontSize); //--- Set font
   int lineHeight = canvasText.TextHeight("A"); //--- Get line height
   text_adjustedLineHeight = lineHeight + 3; //--- Adjust line height
   
   text_visible_height = textAreaHeight; //--- Set visible height
   static string wrappedLines[];       //--- Declare wrapped lines
   static color wrappedColors[];       //--- Declare wrapped colors
   static bool wrapped = false;        //--- Initialize wrapped flag
   bool need_scroll = false;           //--- Initialize scroll need
   int reserved_width = 0;             //--- Initialize reserved width
   if (!wrapped)
     {
      WrapText(text_usage_text, font, fontSize, textAreaWidth, wrappedLines, wrappedColors); //--- Wrap text
      wrapped = true;                  //--- Set wrapped flag
     }
   int numLines = ArraySize(wrappedLines);                 //--- Get number of lines
   text_total_height = numLines * text_adjustedLineHeight; //--- Calculate total height
   need_scroll = text_total_height > text_visible_height;  //--- Check if scroll needed
   if (need_scroll)
     {
      reserved_width = text_track_width; //--- Set reserved width
      textAreaWidth -= reserved_width;   //--- Adjust area width
      WrapText(text_usage_text, font, fontSize, textAreaWidth, wrappedLines, wrappedColors); //--- Rewrap text
      numLines = ArraySize(wrappedLines); //--- Update num lines
      text_total_height = numLines * text_adjustedLineHeight;               //--- Update total height
     }
   text_max_scroll = MathMax(0, text_total_height - text_visible_height);   //--- Calculate max scroll
   text_scroll_visible = need_scroll;                                       //--- Set scroll visible
   text_scroll_pos = MathMax(0, MathMin(text_scroll_pos, text_max_scroll)); //--- Adjust scroll pos
   if (text_scroll_visible)
     {
      int scrollbar_y = 1;                //--- Set scrollbar Y
      int scrollbar_height = textHeight - 2; //--- Calculate scrollbar height
      int scroll_area_height = scrollbar_height - 2 * text_button_size;      //--- Calculate area height
      text_slider_height = TextCalculateSliderHeight();                      //--- Calculate slider height
      int scrollbar_x = textWidth - text_track_width - 1;                    //--- Calculate scrollbar X
      color leader_color = is_dark_theme ? text_leader_color_dark : text_leader_color_light; //--- Get leader color
      uint argb_leader = ColorToARGB(leader_color, 255);                     //--- Convert to ARGB
      canvasText.FillRectangle(scrollbar_x, scrollbar_y, scrollbar_x + text_track_width - 1, scrollbar_y + scrollbar_height - 1, argb_leader); //--- Fill leader
      
      int slider_y = scrollbar_y + text_button_size + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
      
      if (text_scroll_area_hovered)
        {
         color button_bg = is_dark_theme ? text_button_bg_dark : text_button_bg_light;                   //--- Get button bg
         color button_bg_hover = is_dark_theme ? text_button_bg_hover_dark : text_button_bg_hover_light; //--- Get hover bg
         color up_bg = text_scroll_up_hovered ? button_bg_hover : button_bg;                             //--- Determine up bg
         uint argb_up_bg = ColorToARGB(up_bg, 255);                                                      //--- Convert to ARGB
         canvasText.FillRectangle(scrollbar_x, scrollbar_y, scrollbar_x + text_track_width - 1, scrollbar_y + text_button_size - 1, argb_up_bg); //--- Fill up button
         color arrow_color = is_dark_theme ? text_arrow_color_dark : text_arrow_color_light;             //--- Get arrow color
         color arrow_color_disabled = is_dark_theme ? text_arrow_color_disabled_dark : text_arrow_color_disabled_light; //--- Get disabled arrow
         color arrow_color_hover = is_dark_theme ? text_arrow_color_hover_dark : text_arrow_color_hover_light; //--- Get hover arrow
         color up_arrow = (text_scroll_pos == 0) ? arrow_color_disabled : (text_scroll_up_hovered ? arrow_color_hover : arrow_color); //--- Determine up arrow color
         uint argb_up_arrow = ColorToARGB(up_arrow, 255);                                                //--- Convert to ARGB
         canvasText.FontSet("Webdings", 22);                                                             //--- Set font
         int arrow_x = scrollbar_x + text_track_width / 2;                                               //--- Calculate arrow X
         int arrow_y = scrollbar_y + (text_button_size / 2) - (canvasText.TextHeight(CharToString(0x35)) / 2); //--- Calculate arrow Y
         canvasText.TextOut(arrow_x, arrow_y, CharToString(0x35), argb_up_arrow, TA_CENTER);             //--- Draw up arrow
         
         int down_y = scrollbar_y + scrollbar_height - text_button_size;                                 //--- Calculate down Y
         color down_bg = text_scroll_down_hovered ? button_bg_hover : button_bg;                         //--- Determine down bg
         uint argb_down_bg = ColorToARGB(down_bg, 255);                                                  //--- Convert to ARGB
         canvasText.FillRectangle(scrollbar_x, down_y, scrollbar_x + text_track_width - 1, down_y + text_button_size - 1, argb_down_bg); //--- Fill down button
         color down_arrow = (text_scroll_pos >= text_max_scroll) ? arrow_color_disabled : (text_scroll_down_hovered ? arrow_color_hover : arrow_color); //--- Determine down arrow
         uint argb_down_arrow = ColorToARGB(down_arrow, 255);                                            //--- Convert to ARGB
         int down_arrow_x = scrollbar_x + text_track_width / 2;                                          //--- Calculate down arrow X
         int down_arrow_y = down_y + (text_button_size / 2) - (canvasText.TextHeight(CharToString(0x36)) / 2); //--- Calculate down arrow Y
         canvasText.TextOut(down_arrow_x, down_arrow_y, CharToString(0x36), argb_down_arrow, TA_CENTER); //--- Draw down arrow
         
         int slider_x = scrollbar_x + text_scrollbar_margin;                                             //--- Calculate slider X
         int slider_w = text_track_width - 2 * text_scrollbar_margin;                                    //--- Calculate slider width
         int cap_radius = slider_w / 2;                                                                  //--- Calculate cap radius
         color slider_bg_color = is_dark_theme ? text_slider_bg_dark : text_slider_bg_light;             //--- Get slider bg
         color slider_bg_hover_color = is_dark_theme ? text_slider_bg_hover_dark : text_slider_bg_hover_light; //--- Get hover bg
         color slider_bg = text_scroll_slider_hovered || text_movingStateSlider ? slider_bg_hover_color : slider_bg_color; //--- Determine bg
         uint argb_slider = ColorToARGB(slider_bg, 255);                                                 //--- Convert to ARGB

         canvasText.Arc(slider_x + cap_radius, slider_y + cap_radius, cap_radius, cap_radius, DegreesToRadians(180), DegreesToRadians(360), argb_slider); //--- Draw top arc
         canvasText.FillCircle(slider_x + cap_radius, slider_y + cap_radius, cap_radius, argb_slider);   //--- Fill top circle

         canvasText.Arc(slider_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, cap_radius, DegreesToRadians(0), DegreesToRadians(180), argb_slider); //--- Draw bottom arc
         canvasText.FillCircle(slider_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, argb_slider); //--- Fill bottom circle

         canvasText.FillRectangle(slider_x, slider_y + cap_radius, slider_x + slider_w, slider_y + text_slider_height - cap_radius, argb_slider); //--- Fill middle
        }
      else
        {
         int thin_w = text_scrollbar_thin_width;                                                         //--- Set thin width
         int thin_x = scrollbar_x + (text_track_width - thin_w) / 2;                                     //--- Calculate thin X
         int cap_radius = thin_w / 2;                                                                    //--- Calculate cap radius
         color slider_bg_color = is_dark_theme ? text_slider_bg_dark : text_slider_bg_light;             //--- Get bg
         uint argb_slider = ColorToARGB(slider_bg_color, 255);                                           //--- Convert to ARGB

         canvasText.Arc(thin_x + cap_radius, slider_y + cap_radius, cap_radius, cap_radius, DegreesToRadians(180), DegreesToRadians(360), argb_slider); //--- Draw top
         canvasText.FillCircle(thin_x + cap_radius, slider_y + cap_radius, cap_radius, argb_slider);     //--- Fill top

         canvasText.Arc(thin_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, cap_radius, DegreesToRadians(0), DegreesToRadians(180), argb_slider); //--- Draw bottom
         canvasText.FillCircle(thin_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, argb_slider); //--- Fill bottom

         canvasText.FillRectangle(thin_x, slider_y + cap_radius, thin_x + thin_w, slider_y + text_slider_height - cap_radius, argb_slider); //--- Fill middle
        }
     }
   
   color text_base = is_dark_theme ? text_base_dark : text_base_light;                                   //--- Get base color
   for (int line = 0; line < numLines; line++)
     {
      string lineText = wrappedLines[line];   //--- Get line text
      if (StringLen(lineText) == 0) continue; //--- Skip empty
      color lineColor = wrappedColors[line];  //--- Get line color
      if (is_dark_theme) lineColor = (lineColor == clrWhite) ? clrWhite : LightenColor(lineColor, 1.5);  //--- Adjust for dark
      else lineColor = (lineColor == clrWhite) ? clrBlack : DarkenColor(lineColor, 0.7);                 //--- Adjust for light
      int line_y = textAreaY + line * text_adjustedLineHeight - text_scroll_pos;                         //--- Calculate line Y
      if (line_y + text_adjustedLineHeight < 0 || line_y > textAreaHeight) continue;                     //--- Skip out of view
      if (IsHeading(lineText))
        {
         canvasText.FontSet("Arial Bold", fontSize); //--- Set bold font
         lineColor = clrDodgerBlue;                  //--- Set heading color
        }
      else canvasText.FontSet(font, fontSize);       //--- Set regular font
      uint argbText = ColorToARGB(lineColor, 255);   //--- Convert to ARGB
      canvasText.TextOut(textAreaX, line_y, lineText, argbText, TA_LEFT); //--- Draw line
     }
   
   canvasText.Update();                       //--- Update text canvas
  }

ここでは、UpdateTextOnCanvas関数を実装し、canvasTextオブジェクト上にスクロール可能なテキストコンテンツを描画します。この処理では、背景、枠線、動的なテキストの折り返し、テーマに応じた文字色調整、さらにホバー効果付きのカスタムスクロールバーによるナビゲーションを扱います。Eraseをゼロに設定してキャンバスをクリアし、現在の幅/高さを取得し、テーマの背景色(暗いまたは明るい)を決定し、TextBackgroundOpacityPercentから不透明度を100.0×255で割った値をucharにキャストしてARGBに変換し、FillRectangleメソッドを使用して(0,0)から幅/高さマイナス1までの矩形を塗りつぶします。続いて、GetBorderColorから枠線の色を取得し、アルファ255のARGBへ変換して、上下左右の辺をLineで描画します。

パディングは10に設定し、テキスト領域をx=1+padding、y=1、幅は全体幅から枠と左右のパディングを引いた値、高さは枠線を除いた値として計算します。フォントは Arial のサイズ16に設定し、FontSetを使用します。TextHeightで文字「A」の高さから行の高さを取得し、text_adjustedLineHeightに3を加えて行間を調整します。表示可能な高さはtext_visible_heightとして領域の高さを設定します。テキストがまだ折り返されていない場合(静的変数wrappedがfalseの場合)は、WrapText関数を呼び出し、テキスト内容、フォント、サイズ、領域幅を渡してwrappedLinesとwrappedColorsに格納し、その後wrappedをtrueに設定します。行数を配列サイズから取得し、text_total_heightは行数×調整済み行高さとして計算します。総高さが表示可能領域を超えている場合、スクロールが必要かどうかをチェックします。必要であればreserved_widthをtext_track_widthに設定し、領域幅を減らし、テキストを再度折り返しし直して、行数および総高さを更新します。

総高さが表示可能領域を超えている場合、スクロールが必要かどうかをチェックします。必要であればreserved_widthをtext_track_widthに設定し、領域幅を減らし、テキストを再度折り返し直して、行数および総高さを更新します。text_max_scrollは0と(総高さから表示可能高さを引いた値)のうち大きい方として計算します。text_scroll_visibleはスクロールが必要かどうかneed_scrollに設定し、text_scroll_posは0からtext_max_scrollの範囲にクランプします。表示される場合、スクロールバーの位置を計算します。Yは1、高さはテキスト領域から枠を除いた値であり、さらにtext_button_sizeを2倍した分を引いた領域がスライダーの可動範囲になります。text_slider_heightはTextCalculateSliderHeightから取得し、Xは幅からトラック幅と1を引いた位置になります。テーマのリーダー色leader colorを取得し、ARGBに変換してトラックの矩形を塗りつぶします。スライダーのY位置は、スクロール位置の割合に(可動範囲からスライダー高さを引いた値)を掛けて算出します。text_scroll_area_hoveredがtrueであれば、テーマのボタン背景色およびホバー色を取得し、text_scroll_up_hoveredがtrueの場合は上ボタンの背景を決定し、ARGBに変換して上ボタン矩形を塗りつぶします。矢印の色も取得します。通常、無効、ホバーの色を取得し、上方向の矢印色は位置が0のときは無効色、それ以外はホバーまたは通常色になります。ARGBに変換し、Webdingsフォントをサイズ22で設定し、矢印の中心座標を計算し、中央揃えで上矢印0x35をTextOutで描画します。同様に下ボタンについても処理します。下ボタンのYは下端に配置され、背景色やホバー色を取得して塗りつぶします。下矢印の色は位置が最大値のときは無効色、それ以外はホバーまたは通常色になります。中央揃えで下矢印0x36を描画します。

スライダーについては、Xをxにmarginを加えた位置として計算し、幅wはトラック幅からマージンを2倍引いたものになります。キャップ半径はwの半分です。テーマのスライダー背景色およびホバー色を取得し、ホバー中またはドラッグ中である場合に背景を決定し、ARGBに変換します。DegreesToRadiansを用いて角度をラジアンに変換しながら、上端と下端の円弧や円をArcおよびFillCircleで描画し、中間の矩形を塗りつぶします。ホバーしていない場合は、細いスライダーを中央のX位置に描画します。幅は細く、キャップ半径は半分で、テーマ色をARGBに変換して使用します。上端と下端の円弧と円を描画し、細い中央部分を塗りつぶします。

次にテキスト描画をおこないます。テーマのベースカラーを取得し、wrappedLinesをループします。空行はスキップします。色を取得して調整します。白または黒の場合はそのまま、それ以外はダークテーマでは1.5倍明るくし、ライトテーマでは0.7倍暗くします。各行のY位置はarea yにline × adjustedLineHeightを加え、そこからtext_scroll_posを引いた値として計算し、表示領域外(負の値または領域を超える場合)はスキップします。IsHeadingがtrueの場合は太字フォントとドジャーブルー色を設定し、それ以外は通常フォントを使用します。ARGBに変換した後、左揃えでテキストを描画します。最後に、Updateを使ってキャンバスを更新し、テキストを表示します。この関数はキャンバス領域を定義した後の初期化処理の中で呼び出されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Other panels logic
   
   if (EnableTextPanel)
     {
      int textY = currentCanvasY + header_height + gap_y + currentHeight + PanelGap; //--- Calculate text Y
      if (!canvasText.CreateBitmapLabel(0, 0, canvasTextName, currentCanvasX, textY, header_width, TextPanelHeight, COLOR_FORMAT_ARGB_NORMALIZE))
        {
         Print("Failed to create Text Canvas");       //--- Log text creation failure
        }
      textCreated = true;                             //--- Set text created flag
     }
   
   if (EnableTextPanel) UpdateTextOnCanvas();         //--- Update text if enabled
   
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); //--- Enable mouse wheel events
   ChartRedraw();                                     //--- Redraw chart
   return(INIT_SUCCEEDED);                            //--- Return success
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if (textCreated) canvasText.Destroy();             //--- Destroy text if created
   ChartRedraw();                                     //--- Redraw chart
  }


//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBarTime = 0;                   //--- Initialize last time
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time
   if (currentBarTime > lastBarTime)
     {
      UpdateGraphOnCanvas();                         //--- Update graph
      if (EnableStatsPanel) UpdateStatsOnCanvas();   //--- Update stats
      if (EnableTextPanel) UpdateTextOnCanvas();     //--- Update text
      ChartRedraw();                                 //--- Redraw chart
      lastBarTime = currentBarTime;                  //--- Update last time
     }
  }

まず、 OnInitイベントハンドラを修正し、EnableTextPanelがtrueの場合にテキストパネルの作成を組み込みます。他のパネルのロジックの後で、y位置をcurrentCanvasYにheader_height、gap_y、currentHeight、PanelGapを加えた値として計算し、CreateBitmapLabelを使用してサブウィンドウ0にビットマップラベルを作成します。名前はcanvasTextName、位置とヘッダー幅、TextPanelHeight、およびCOLOR_FORMAT_ARGB_NORMALIZEを指定します。作成に失敗した場合はエラーを出力し、成功時にはtextCreatedをtrueに設定します。さらに「EnableTextPanel」が有効な場合、「UpdateTextOnCanvas」を呼び出して初期描画をおこないます。チャートのホイールイベントを検知するためにChartSetIntegerを使用し、CHART_EVENT_MOUSE_WHEELをtrueに設定してマウスホイールイベントを有効化します。これによりスクロール操作の検出が可能になります。その後ChartRedrawを呼び出し、INIT_SUCCEEDEDを返します。

次にOnDeinitイベントハンドラを更新し、textCreatedがtrueの場合に限りcanvasText.Destroyを呼び出してテキストキャンバスを破棄し、その後再描画をおこないます。OnTickイベントハンドラも調整し、新しいバー発生時にテキストパネルの更新を含めます。static変数「lastBarTime」を0で初期化し、iTimeを使用して現在のバー時間を取得します。これが前回より大きい場合、新しいバーと判断し、「UpdateGraphOnCanvas」を呼び出し、「EnableStatsPanel」が有効であれば「UpdateStatsOnCanvas」も呼び出し、「EnableTextPanel」が有効であれば「UpdateTextOnCanvas」も呼び出します。その後ChartRedrawを実行し、lastBarTimeを更新します。コンパイルすると、次の結果が得られます。

描画されたテキストパネル

画像から確認できるように、テキストパネルが正常に描画されています。次に必要なのは、現在描画しているテキストのスクロールを処理するためにチャートイベントハンドラを更新することです。以下がそのロジックです。ここでは最終結果を得るためにおこなった変更のみを記載しており、以前のバージョンで実装していた他のロジックは削除しています。

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if (id == CHARTEVENT_CHART_CHANGE)
     {
      //--- Existing logic
      
      if (EnableTextPanel) UpdateTextOnCanvas(); //--- Update text
      ChartRedraw();                             //--- Redraw chart
     }
   else if (id == CHARTEVENT_MOUSE_MOVE)
     {
      //--- Existing logic
      
      if (EnableTextPanel && !panels_minimized)
        {
         int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE); //--- Get text X
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y
         int text_canvas_w = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XSIZE);     //--- Get width
         int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE);     //--- Get height
         bool is_over_text = (mouse_x >= text_canvas_x && mouse_x <= text_canvas_x + text_canvas_w &&
                              mouse_y >= text_canvas_y && mouse_y <= text_canvas_y + text_canvas_h); //--- Check over text
         bool prev_scroll_hovered = text_scroll_area_hovered;                             //--- Store prev scroll hover
         text_scroll_area_hovered = false;         //--- Reset area hover
         if (is_over_text)
           {
            int local_x = mouse_x - text_canvas_x; //--- Get local X
            int local_y = mouse_y - text_canvas_y; //--- Get local Y
            if (local_x >= text_canvas_w - text_track_width - 1)
              {
               text_scroll_area_hovered = true;    //--- Set area hover
              }
            bool prev_up = text_scroll_up_hovered; //--- Store prev up
            bool prev_down = text_scroll_down_hovered;     //--- Store prev down
            bool prev_slider = text_scroll_slider_hovered; //--- Store prev slider
            TextUpdateHoverEffects(local_x, local_y);      //--- Update hovers
            if (prev_scroll_hovered != text_scroll_area_hovered || prev_up != text_scroll_up_hovered || prev_down != text_scroll_down_hovered || prev_slider != text_scroll_slider_hovered)
              {
               UpdateTextOnCanvas();               //--- Update text
               ChartRedraw();                      //--- Redraw chart
              }
            text_mouse_in_body = (local_x < text_canvas_w - text_track_width - 1); //--- Set body flag
           }
         else
           {
            bool need_redraw = prev_scroll_hovered || text_scroll_up_hovered || text_scroll_down_hovered || text_scroll_slider_hovered; //--- Check redraw need
            if (need_redraw)
              {
               text_scroll_area_hovered = false;   //--- Reset area
               text_scroll_up_hovered = false;     //--- Reset up
               text_scroll_down_hovered = false;   //--- Reset down
               text_scroll_slider_hovered = false; //--- Reset slider
               UpdateTextOnCanvas();               //--- Update text
               ChartRedraw();                      //--- Redraw chart
              }
            text_mouse_in_body = false;            //--- Reset body flag
           }
         // New: Toggle CHART_MOUSE_SCROLL on enter/leave text body
         if (text_mouse_in_body != prev_text_mouse_in_body)
           {
            ChartSetInteger(0, CHART_MOUSE_SCROLL, !text_mouse_in_body); //--- Toggle scroll
            prev_text_mouse_in_body = text_mouse_in_body;                //--- Update prev
           }
        }
      
      if (mouse_state == 1 && prev_mouse_state == 0)
        {
         
         //--- Existing logic
         
         if (EnableTextPanel && !panels_minimized && text_scroll_visible && text_scroll_area_hovered)
           {
            int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE); //--- Get text X
            int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y
            int local_x = mouse_x - text_canvas_x;  //--- Get local X
            int local_y = mouse_y - text_canvas_y;  //--- Get local Y
            int scrollbar_x = canvasText.Width() - text_track_width - 1;                     //--- Get scrollbar X
            int scrollbar_y = 1;                    //--- Set scrollbar Y
            int scrollbar_height = TextPanelHeight - 2;                                      //--- Get height
            int scroll_area_y = scrollbar_y + text_button_size;                              //--- Calculate area Y
            int scroll_area_height = scrollbar_height - 2 * text_button_size;                //--- Calculate area height
            int slider_y = scroll_area_y + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
            if (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1)
              {
               if (local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1)
                 {
                  TextScrollUp();                   //--- Scroll up
                 }
               else if (local_y >= scrollbar_y + scrollbar_height - text_button_size && local_y <= scrollbar_y + scrollbar_height - 1)
                 {
                  TextScrollDown();                 //--- Scroll down
                 }
               else if (local_y >= scroll_area_y && local_y <= scroll_area_y + scroll_area_height - 1)
                 {
                  if (local_y >= slider_y && local_y <= slider_y + text_slider_height - 1)
                    {
                     text_movingStateSlider = true;  //--- Set moving slider
                     text_mlbDownY_Slider = local_y; //--- Set down Y
                     text_mlbDown_YD_Slider = slider_y;                                      //--- Set down YD
                     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);                          //--- Disable scroll
                    }
                  else
                    {
                     int new_slider_y = local_y - text_slider_height / 2;                    //--- Calculate new Y
                     new_slider_y = MathMax(scroll_area_y, MathMin(new_slider_y, scroll_area_y + scroll_area_height - text_slider_height)); //--- Clamp Y
                     double ratio = (double)(new_slider_y - scroll_area_y) / (scroll_area_height - text_slider_height); //--- Calculate ratio
                     text_scroll_pos = (int)MathRound(ratio * text_max_scroll);              //--- Update pos
                    }
                  UpdateTextOnCanvas();             //--- Update text
                  ChartRedraw();                    //--- Redraw chart
                 }
              }
           }
        }

      else if (text_movingStateSlider && mouse_state == 1)
        {
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE);    //--- Get text Y
         int local_y = mouse_y - text_canvas_y;                                              //--- Get local Y
         int delta_y = local_y - text_mlbDownY_Slider;                                       //--- Calculate delta Y
         int new_slider_y = text_mlbDown_YD_Slider + delta_y;                                //--- Calculate new slider Y
         int scrollbar_y = 1;                                                                //--- Set scrollbar Y
         int scrollbar_height = TextPanelHeight - 2;                                         //--- Get height
         int slider_min_y = scrollbar_y + text_button_size;                                  //--- Calculate min Y
         int slider_max_y = scrollbar_y + scrollbar_height - text_button_size - text_slider_height;  //--- Calculate max Y
         new_slider_y = MathMax(slider_min_y, MathMin(new_slider_y, slider_max_y));                   //--- Clamp Y
         double scroll_ratio = (double)(new_slider_y - slider_min_y) / (slider_max_y - slider_min_y); //--- Calculate ratio
         int new_scroll_pos = (int)MathRound(scroll_ratio * text_max_scroll);                //--- Calculate new pos
         if (new_scroll_pos != text_scroll_pos)
           {
            text_scroll_pos = new_scroll_pos; //--- Update pos
            UpdateTextOnCanvas();             //--- Update text
            ChartRedraw();                    //--- Redraw chart
           }
        }
      else if (mouse_state == 0 && prev_mouse_state == 1)
        {
         //--- Existing logic
         
         if (text_movingStateSlider)
           {
            text_movingStateSlider = false;  //--- Reset slider moving
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll
            UpdateTextOnCanvas();            //--- Update text
            ChartRedraw();                   //--- Redraw chart
           }
        }
      
      last_mouse_x = mouse_x;               //--- Update last X
      last_mouse_y = mouse_y;               //--- Update last Y
      prev_mouse_state = mouse_state;       //--- Update prev state
     }
   else if (id == CHARTEVENT_MOUSE_WHEEL)
     {
      int flg_keys = (int)(lparam >> 32);   //--- Get keys
      int mx = (int)(short)lparam;          //--- Get X
      int my = (int)(short)(lparam >> 16);  //--- Get Y
      int delta = (int)dparam;              //--- Get delta
      
      if (EnableTextPanel && !panels_minimized && text_scroll_visible)
        {
         int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE);       //--- Get text X
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE);       //--- Get text Y
         int text_canvas_w = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XSIZE);           //--- Get width
         int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE);           //--- Get height
         bool is_over_text_body = (mx >= text_canvas_x && mx <= text_canvas_x + text_canvas_w - text_track_width &&
                                   my >= text_canvas_y && my <= text_canvas_y + text_canvas_h); //--- Check over body
         if (is_over_text_body)
           {
            int scroll_step = 20;            //--- Set step
            text_scroll_pos += (delta > 0 ? -scroll_step : scroll_step);                        //--- Adjust pos
            text_scroll_pos = MathMax(0, MathMin(text_scroll_pos, text_max_scroll));            //--- Clamp pos
            UpdateTextOnCanvas();            //--- Update text
            
            int current_scale = (int)ChartGetInteger(0, CHART_SCALE);                           //--- Get scale
            int adjust = (delta > 0 ? 1 : -1); // Swap to (delta > 0 ? -1 : 1) if wheel direction is opposite
            int revert_scale = current_scale + adjust;                                          //--- Calculate revert
            revert_scale = MathMax(0, MathMin(5, revert_scale));                                //--- Clamp scale
            ChartSetInteger(0, CHART_SCALE, revert_scale);                                      //--- Set scale
            
            ChartRedraw();                    //--- Redraw chart
           }
        }
     }
  }

ここでは、 OnChartEventイベントハンドラを修正し、EnableTextPanelがtrueかつpanels_minimizedではない場合にテキストパネルのインタラクティブ機能を追加します。ObjectGetIntegerを使用してテキストキャンバスの位置とサイズを取得します。取得するプロパティはOBJPROP_XDISTANCE、OBJPROP_YDISTANCE、OBJPROP_XSIZE、OBJPROP_YSIZEです。その後、マウスがテキスト領域全体の上にあるかを判定します。前回のtext_scroll_area_hoveredの状態を保存し、いったんfalseにリセットします。マウスが領域内にある場合、マウス座標からキャンバス位置を引いてローカル座標を計算します。ローカルxがスクロールバー領域、つまり幅からtext_track_widthと1を引いた位置から右端までの範囲にある場合、スクロールバー領域上にあると判定します。

前回の上ボタン、下ボタン、スライダーのホバー状態を保存し、TextUpdateHoverEffectsをローカル座標で呼び出してホバー状態を更新します。ホバー状態または領域状態に変化があった場合はUpdateTextOnCanvasを呼び出して再描画します。ローカルxがスクロールバーより左側の場合は、text_mouse_in_bodyをtrueに設定します。一方でテキスト領域外にあり、かつ前回の状態変化により更新が必要な場合は、領域、上ボタン、下ボタン、スライダーのホバー状態をすべてfalseにリセットし、テキストを更新して再描画します。text_mouse_in_bodyが前回値から変化した場合は、ChartSetIntegerを使用してCHART_MOUSE_SCROLLを切り替えます。テキスト内ではfalse、テキスト外ではtrueに設定し、チャートのズーム操作とホイール操作の干渉を防ぎます。その後、前回状態を更新します。

マウスダウン処理です。stateが1でpreviousが0の場合、テキストパネルが有効で、最小化されておらず、スクロールバー領域がホバーされている場合に処理をおこないます。ローカルx、ローカルy、スクロールバーのx、y、高さ、エリアy、高さ、スライダーyを取得します。ローカルxがスクロールバー領域内にある場合、ローカルyが上ボタン領域であればTextScrollUpを呼び出し、下ボタン領域であればTextScrollDownを呼び出します。それ以外の場合でエリア内にある場合、スライダー上であればtext_movingStateSliderをtrueに設定し、text_mlbDownY_Sliderにローカルyを保存し、text_mlbDown_YD_Sliderにスライダーyを保存し、通常スクロールを無効化します。スライダー上でない場合は、クリック位置から新しいスライダー位置を計算します。ローカルyからtext_slider_heightの半分を引いた値を基準にし、スライダーの移動範囲であるarea yからarea y+height-text_slider_heightの間にクランプします。その後ratioを(new-area y) divided by(height-text_slider_height)として計算し、text_scroll_posをrounded ratio times text_max_scrollに設定します。そしてテキスト更新と再描画をおこないます。

次にドラッグ中の処理です。text_movingStateSliderがtrueでstateが1(押下継続)の場合、マウスのローカルyを取得し、開始位置との差分deltaを計算します。new slider yをtext_mlbDown_YD_Slider+deltaとして算出し、これをスライダー範囲にクランプします。最小値はy+text_button_size、最大値はy+height-text_button_size-text_slider_heightです。ratioを(new-min) divided by(max-min)として計算し、新しい位置をrounded ratio times text_max_scrollとして算出します。現在のtext_scroll_posと異なる場合のみ更新し、テキスト更新と再描画をおこないます。

マウスアップ処理ではstateが0でpreviousが1の場合、text_movingStateSliderがfalseであればスクロールを有効に戻し、テキスト更新と再描画をおこないます。さらにチャート変更イベントでは、EnableTextPanelがtrueの場合、再描画前にUpdateTextOnCanvasを呼び出す処理を追加します。マウスホイールイベントでは、lparamからflg_keysをshift 32で取得し、mxとmyをshortとして抽出し、dparamをdeltaとして整数として扱います。text scroll visibleがtrueの場合に処理をおこない、マウスがテキスト本体領域内にあるかを判定します。テキストまたはスクロールが表示可能な状態であれば、テキストのプロパティを取得し、マウスが本体領域上にあるかを判定します(mx,myがxからx + width - text_track_widthの範囲かつyからy+heightの範囲内)。もし該当する場合、text_scroll_posをホイールの入力に応じて20ずつ増減させ(deltaが正なら減少、負なら増加)、0からtext_max_scrollの範囲にクランプします。その後テキストを更新します。さらにChartGetIntegerを使用して現在のチャートスケール(CHART_SCALE)を取得し、ホイール方向に応じて+1または-1で調整し、0から5の範囲にクランプします。そしてChartSetIntegerで再設定することでズーム動作を抑制します。最後に再描画をおこないます。コンパイルすると、次の結果が得られます。

テキストCANVASテスト

可視化結果から、すべてのインタラクションを備えたテキストパネルを追加することで、キャンバスベースのダッシュボードが拡張されていることが確認できます。残るは、システムの動作確認、つまり前のセクションでおこなったテストです。


バックテスト

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

完全なCANVASダッシュボードバックテスト


結論

結論として、MQL5におけるcanvasベースの価格ダッシュボードは、使用ガイド用のピクセルパーフェクトなスクロール可能テキストキャンバスを実装することで強化されました。これにより、MQL5標準機能の制約を回避し、アンチエイリアスによる滑らかな描画、ホバー時に拡張される角丸カスタムスクロールバー、ボタンとスライダーによるナビゲーション、テーマ対応要素、色調整を伴う動的なテキスト折り返し、さらにホイール、クリック、ドラッグ操作への対応が実現されています。このシステムはテキストパネルを既存のグラフおよび統計表示と統合し、ドラッグ、サイズ変更、テーマ変更、最小化機能を維持しながら、効率的なイベント駆動更新によって包括的なモニタリングツールとして機能します。このピクセルパーフェクトなスクロールテキストキャンバス拡張により、ダッシュボード内で詳細なユーザーガイダンスを提供できるようになり、取引環境におけるさらなる最適化の準備が整いました。取引をお楽しみください。

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

添付されたファイル |
MQL5でカスタムインジケータを作成する(第6回):平滑化、色相シフト、マルチタイムフレーム対応を備えたRSI計算の拡張 MQL5でカスタムインジケータを作成する(第6回):平滑化、色相シフト、マルチタイムフレーム対応を備えたRSI計算の拡張
MQL5で多用途なRSIインジケータを構築します。このインジケータは複数のバリエーション、データソース、平滑化手法をサポートし、より高度な分析を可能にします。さらに、視覚的な色表現のための色相シフト、買われすぎ・売られすぎゾーンの動的境界、トレンドアラート用の通知機能を追加します。また、補間を伴うマルチタイムフレーム対応も実装し、異なる時間足のRSI値を補間によって滑らかに対応付けるカスタマイズ可能なRSIツールを提供します。
MQL5入門(第37回):MQL5のAPIとWebRequest関数の習得(XI) MQL5入門(第37回):MQL5のAPIとWebRequest関数の習得(XI)
MQL5を使用してBinance APIに認証付きリクエストを送信し、アカウント内の全資産の残高情報を取得する方法を解説します。APIキー、サーバー時刻、署名を利用して安全にアカウント情報へアクセスし、そのレスポンスをファイルへ保存して後で活用する方法を学びます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5標準ライブラリエクスプローラー(第6回):生成されたエキスパートアドバイザーの最適化 MQL5標準ライブラリエクスプローラー(第6回):生成されたエキスパートアドバイザーの最適化
前回開発したマルチシグナルエキスパートアドバイザーを引き続き取り上げ、利用可能な最適化手法の検討と適用をおこないます。その目的は、過去データに基づく体系的な最適化を通じて、EAの取引パフォーマンスを有意に向上させることが可能かどうかを検証することです。