MQL5でカスタムインジケーターを作成する(第5回):WaveTrend Crossover Evolution:Canvasを用いたフォグ状グラデーション、シグナルバブル、リスク管理
はじめに
前回の記事(第4回)では、MetaQuotes Language 5 (MQL5)を用いてSmart WaveTrend Crossoverインジケーターを開発しました。このインジケーターは、シグナル用とトレンドフィルタ用の2つのオシレーターを備え、クロスオーバーに基づく売買シグナルを生成するとともに、任意でトレンド確認もおこないます。第5回では、このWaveTrend Crossoverインジケーターをさらに発展させ、Canvasベースの描画によるフォグ状グラデーション、ブレイクアウトを検出するシグナルボックス、視覚的アラートとしてのカスタマイズ可能なバブルや三角形表示を追加します。また、リスク管理機能として、ローソク足倍率やパーセンテージに基づいて算出される動的なテイクプロフィット(TP)およびストップロス(SL)を導入し、ラインやテーブル形式で表示できるようにします。この進化により、フォグ状グラデーションによる相場環境の可視化に加え、トレンドフィルタリング、ボックス延長、そしてリスク計算機能を統合した高度な視覚表現が可能になります。本記事では以下のトピックを扱います。
最終的に、本記事を通じてカスタマイズ可能な拡張WaveTrendクロスオーバーインジケーターを構築し、ビジュアル要素とリスク管理機能を統合した実用的なMQL5ツールを完成させることを目指します。それでは解説に進みます。
ビジュアルおよびリスク機能を備えた拡張CanvasベースWaveTrend Crossoverフレーム
拡張されたCanvasベースのWaveTrendクロスオーバーフレームワークは、コアとなるモメンタムオシレーターを基盤としつつ、ビジュアルオーバーレイとリスク管理ツールを組み込むことで、より直感的で実用的な取引インターフェースになります。本構造は、従来通りデュアルWaveTrend構成を維持しており、1つはクロスオーバーを検出するための高感度オシレーターとして機能し、エントリーシグナルを生成します。もう1つはより遅い設定でトレンドフィルタとして機能し、ノイズを抑えつつ方向性を確認します。これに加えて、クロスオーバーポイント周辺にシグナルボックスを生成し、価格がそのレンジを上抜け/下抜けした際にボックスをクローズし、ブレイクアウトを検出します。単なるクロスオーバーではなく確認されたモメンタム変化を捉える設計です。さらに、フォグ状のグラデーションをチャート上に重ね、透明度の変化でトレンドの強さを表現します。これにより相場状況をひと目で把握できます。さらに、買いと売りのシグナルはラベル付きのバブルやシンプルな三角形で表示され、視認性の高いアラートとして機能します。
上昇トレンドのシナリオでは、シグナル用オシレーターが上方向にクロスした場合、必要に応じてスローモードのオシレーターによる上昇トレンド確認を条件として、該当バーのレンジ周辺にボックスを生成します。その後、価格が上方向にブレイクした際に、ボックスの方向と一致していれば買いシグナルが発生し、視覚的要素によってその機会が強調されます。一方で下降トレンドでは、下方向クロスによってボックスが形成され、下方向へのブレイクアウトが発生した場合に売りシグナルが生成されます。これにより、反転だけでなくトレンド継続局面もノイズを抑えながら捉えることが可能になります。リスク管理機能としては、平均的なローソク足サイズまたはパーセンテージ変動を基準にTPおよびSLを動的に計算し、それらをラインおよびテーブルとしてチャート上に表示します。これによって、ポジションサイズ設計やエグジット計画を視覚的に補助できるようになります。こうすることで、ヒット率を把握することができます。
MQL5 Canvasライブラリを活用して、バー間を補間する霧状のグラデーションを描画し、滑らかなトレンドの視覚化を実現します。また、シグナルボックスを追跡してブレイクアウトを検出し、平均ローソク足乗数を使用したオプションの拡張機能でブレイクアウトを決済します。さらに、ラベル付きバブルなどの柔軟なシグナルタイプを提供し、視認性を向上させます。ユーザー定義モードで利益確定と損切りを設定し、リスクレベルを計算します。これらすべてを実現すると同時に、チャートの変更時に効率的な再描画を保証します。以下に想定されるビジュアル表示の例を示します。

MQL5での実装
拡張機能の実装を開始するにあたり、まず最初におこなうべきことは、追加予定の機能に対応するためにインジケーター内部のバッファ構成を調整し、より多くのデータを保持できるようにすることです。
//+------------------------------------------------------------------+ //| 1. Smart WaveTrend Crossover 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 indicator_chart_window #property indicator_buffers 28 #property indicator_plots 3 #property indicator_label1 "Colored Candles" #property indicator_type1 DRAW_COLOR_CANDLES #property indicator_color1 clrTeal, clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 #property indicator_label2 "Buy Signals" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrForestGreen #property indicator_style2 STYLE_SOLID #property indicator_width2 1 #property indicator_label3 "Sell Signals" #property indicator_type3 DRAW_ARROW #property indicator_color3 clrOrangeRed #property indicator_style3 STYLE_SOLID #property indicator_width3 1
ここでは、追加機能に対応するためにインジケーターバッファ数を23から28へ増加させます。これで、拡張された機能で必要となる追加計算データを格納できるようになります。変更箇所は分かりやすくするために該当行を強調しています。次におこなうのは、チャート上でのカスタム描画を可能にするためのCanvasライブラリのインクルードです。標準のMQL5プロット機能では実現できないフォグ状グラデーション、カスタムボックス、ラベルなどの高度なビジュアル要素を描画するために必要となります。そのために、以下の手法を使用します。
#include <Canvas/Canvas.mqh>
このように#include <Canvas/Canvas.mqh>を追加すると、チャート上にカスタムグラフィックを描画するためのクラスおよび関数が利用可能になります。その結果、標準プロットに依存せず、フォグ表現やボックス描画、ラベル表示などをプログラム的に制御でき、シグナルやトレンドの視覚表現を大幅に強化できます。続いて、インターフェースからより柔軟に制御できるように、追加の入力パラメータを定義していきます。
input group "Signal Settings" input bool use_trend_filter = true; // Use Trend Filter for Boxes? enum signal_options { Triangles, // Triangles Labels_Buy_Sell // Labels Buy Sell }; input signal_options signal_type = Labels_Buy_Sell; // Signal Type input color signal_buy_col = clrForestGreen; // Buy Signal Color input color signal_sell_col = clrOrangeRed; // Sell Signal Color input bool show_only_matching = true; // Show Only Matching Signals? input bool use_box_multiplier = false; // Extend Box by Average Candle Size? input double box_multiplier = 1.0; // Box Extension Multiplier input int base_offset = 10; // Base Signal Offset from Candle input group "Box Settings" input color box_bull_fill = clrBlue; // Box Bull Fill Color input color box_bear_fill = clrGold; // Box Bear Fill Color input int box_fill_transp = 80; // Box Fill Transparency (0-100) input group "Fog" input bool show_fog = true; // Fog input double offset_mult = 0.7; // Fog Height × Avg Candle input int base_transp = 80; // Base Transparency input int transp_inc = 4; // Transparency Increment input group "Risk Management" input bool showTPSL = true; // Show TP/SL Levels enum tp_sl_modes { Candle_Multiplier, // Candle Multiplier Percentage // Percentage }; input tp_sl_modes tpSlMode = Candle_Multiplier; // TP/SL Calculation Mode input int tp_sl_length = 50; // Average Candle Length Period input double tp1Multiplier = 2.0; // TP1× input double tp2Multiplier = 3.0; // TP2× input double tp3Multiplier = 4.0; // TP3× input double slMultiplier = 2.0; // SL× input double tp1Percent = 2.0; // TP1 % input double tp2Percent = 3.0; // TP2 % input double tp3Percent = 4.0; // TP3 % input double slPercent = 1.5; // SL %
ユーザー入力は引き続きグループ化されたセクションとして定義し、拡張機能を柔軟にカスタマイズできる構成にしています。まずSignal Settingsグループでは、ボックスベースのシグナルにトレンドフィルタを適用するかどうかを制御する真偽値入力をデフォルトtrueで用意します。続いてsignal_optionsの列挙型を追加し、シグナル表示形式としてTriangles(矢印表示)またはLabels_Buy_Sell(テキストバブル表示)を選択できるようにし、デフォルトは後者に設定して視認性の高いラベル表示を採用します。さらに、買いと売りのシグナルの色設定としてそれぞれフォレストグリーンとオレンジレッドをデフォルト値で指定します。また、ボックス方向と一致するシグナルのみを表示するためのフィルタ機能を持つ真偽値をデフォルト有効で追加します。ボックス拡張関連では、平均ローソク足サイズに基づいてボックスを延長する機能を制御する真偽値をデフォルト無効で追加し、その拡張倍率を指定するdouble値(1.0)と、シグナル位置調整用のオフセット値(10ポイント)を整数で設定します。
次にBox Settingsグループでは、強気のボックスおよび弱気のボックスの塗りつぶしカラーをそれぞれブルーとゴールドで定義し、さらに透明度を0〜100の範囲で制御する整数入力を追加します。この値はデフォルト80とし、ボックス描画の不透明度を調整するために使用します。Fogグループでは、フォグ状グラデーション表示の有効化を制御する真偽値をデフォルトtrueで設定します。さらに、平均ローソク足サイズに基づいてフォグの高さをスケーリングする倍率(0.7)、グラデーションのベース透明度(80)、および滑らかなフェードを実現するための透明度増分(4)を定義します。最後にRisk Managementグループでは、TPおよびSL表示の有効化を制御する真偽値をデフォルトtrueで追加します。さらに、計算方式としてCandle_MultiplierまたはPercentageを選択できるtp_sl_modes列挙型を導入し、デフォルトは前者とします。加えて、平均ローソク足サイズの算出期間(50)を整数で設定し、TPおよびSLについては倍率またはパーセンテージをdouble型で定義します。たとえば、第一TPの倍率は2.0、SLのパーセンテージは1.5とし、複数段階のリスク設定に対応します。
次に、ボックス拡張、フォグの高さ、TP/SL計算といった新機能で必要となるボラティリティベースのスケーリングをおこなうため、平均ローソク足サイズを保持するグローバルバッファを拡張します。このバッファは、ボックスの延長距離、フォグの表示範囲、TPおよびSLの計算などに利用され、すべての視覚要素と価格レベルを動的にスケーリングするための基準値として機能します。
double avg_candle_size[]; //--- Average candle size buffer
これに加えて、グローバル変数も拡張する必要があります。具体的には、Canvasオブジェクトの管理、動的描画のためのチャートプロパティ、再描画タイミングを制御するタイムスタンプ、ボックスおよびシグナル情報を保持する構造体、それらを格納する配列、TP/SL表示用のラインやテーブル名、ボックス拡張期間、オブジェクト識別用プレフィックス、そしてフォントサイズなどを定義します。これらの変数は、カスタム描画の管理、チャートの可視領域追跡による最適化、ボックスやシグナルの永続データ保持、そしてTP/SL表示の制御に使用されます。結果として、効率的な再描画処理とスケーラブルなビジュアル描画を実現するための基盤となります。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ CCanvas obj_Canvas; //--- Canvas object int currentChartWidth = 0; //--- Current chart width int currentChartHeight = 0; //--- Current chart height int currentChartScale = 0; //--- Current chart scale int firstVisibleBarIndex = 0; //--- First visible bar index int visibleBarsCount = 0; //--- Visible bars count double minPrice = 0.0; //--- Minimum price double maxPrice = 0.0; //--- Maximum price static datetime lastRedrawTime = 0; //--- Last redraw time //+------------------------------------------------------------------+ //| Box information structure | //+------------------------------------------------------------------+ struct BoxInfo { datetime left_time; // Store left time datetime right_time; // Store right time double top; // Store top price double bottom; // Store bottom price int dir; // Store direction }; BoxInfo all_boxes[]; //--- All boxes array //+------------------------------------------------------------------+ //| Signal information structure | //+------------------------------------------------------------------+ struct SignalInfo { datetime time; // Store signal time int dir; // Store signal direction }; SignalInfo all_signals[]; //--- All signals array string slLine = "SL_Line"; //--- SL line name string tp1Line = "TP1_Line"; //--- TP1 line name string tp2Line = "TP2_Line"; //--- TP2 line name string tp3Line = "TP3_Line"; //--- TP3 line name string tpSlTableObjects[11]; //--- TP/SL table objects long extendSeconds = PeriodSeconds() * 100; //--- Extend seconds string objPrefix = "SWTC_"; //--- Object prefix int current_font_size; //--- Current font size
ここでは、インジケーターの状態管理およびカスタムビジュアル制御のためにグローバル変数を宣言します。まず、Canvasベースの描画処理を全体で扱うために、CCanvasクラスのインスタンスとしてobj_Canvasを用意します。次に、チャートの動的描画に必要な情報を保持するための整数変数として、現在のチャート幅、高さ、スケール、最初に表示されているバーのインデックス、そして可視バー数を定義します。また、可視チャート領域の最小価格および最大価格を保持するdouble型変数を用意し、再描画時の動的調整に対応できるようにします。さらに、最後にCanvasを更新した時刻を記録するための静的datetime変数lastRedrawTimeを0で初期化し、描画頻度を最適化します。
シグナルボックス管理のためにBoxInfo構造体を定義します。この構造体には、左端および右端のタイムスタンプ、上限および下限価格、そして方向を示す整数値を含めます。その後、複数のボックス情報を管理するための配列all_boxesを用意します。
同様に、シグナル管理用としてSignalInfo構造体を定義し、タイムスタンプと方向情報を保持します。生成されたシグナルを表示用に保持するための配列all_signalsも併せて用意します。リスク管理表示のために、TPおよびSLラインの命名用文字列を定義します。例えばストップロス用としてSL_Lineを設定し、リスク管理テーブルのオブジェクト参照用としてサイズ11の文字列配列tpSlTableObjectsを用意します。さらに、チャート周期に基づいてPeriodSecondsに100を掛けた値を用い、表示要素の延長期間を制御するためのlong型変数extendSecondsを設定します。最後に、オブジェクト名の衝突を防ぐためのプレフィックスとしてSWTC_を定義し、チャートスケールに応じてテキストサイズを動的に調整するための整数変数current_font_sizeを用意します。次に、オブジェクトの可視化のためのヘルパー関数をいくつか定義します。
//+------------------------------------------------------------------+ //| Darken color | //+------------------------------------------------------------------+ color DarkenColor(color c, double factor = 0.5) { uchar r = uchar((c & 0xFF) * factor); //--- Compute red component uchar g = uchar(((c >> 8) & 0xFF) * factor); //--- Compute green component uchar b = uchar(((c >> 16) & 0xFF) * factor); //--- Compute blue component return (color)((b << 16) | (g << 8) | r); //--- Return darkened color } //+------------------------------------------------------------------+ //| Draw rectangle label | //+------------------------------------------------------------------+ bool drawRectangleLabel(string objectName, int xDistance, int yDistance, int xSize, int ySize, color rectColor, int borderType = BORDER_FLAT, bool back = true) { bool objectExists = (ObjectFind(0, objectName) >= 0); //--- Check if object exists if (!objectExists) { //--- Handle new object if (!ObjectCreate(0, objectName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print("Failed to create ", objectName); //--- Log failure return false; //--- Return failure } } ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance ObjectSetInteger(0, objectName, OBJPROP_XSIZE, xSize); //--- Set x size ObjectSetInteger(0, objectName, OBJPROP_YSIZE, ySize); //--- Set y size ObjectSetInteger(0, objectName, OBJPROP_COLOR, rectColor); //--- Set color ObjectSetInteger(0, objectName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objectName, OBJPROP_BACK, back); //--- Set background ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false); //--- Disable selected return true; //--- Return success } //+------------------------------------------------------------------+ //| Draw label | //+------------------------------------------------------------------+ bool drawLabel(string objectName, int xDistance, int yDistance, string text, color labelColor) { bool objectExists = (ObjectFind(0, objectName) >= 0); //--- Check if object exists if (!objectExists) { //--- Handle new object if (!ObjectCreate(0, objectName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print("Failed to create ", objectName); //--- Log failure return false; //--- Return failure } } ObjectSetInteger(0, objectName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance ObjectSetInteger(0, objectName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance ObjectSetString(0, objectName, OBJPROP_TEXT, text); //--- Set text ObjectSetInteger(0, objectName, OBJPROP_COLOR, labelColor); //--- Set color ObjectSetInteger(0, objectName, OBJPROP_FONTSIZE, 10); //--- Set font size ObjectSetString(0, objectName, OBJPROP_FONT, "Arial"); //--- Set font ObjectSetInteger(0, objectName, OBJPROP_BACK, false); //--- Disable background ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false); //--- Disable selected return true; //--- Return success }
まずDarkenColor関数では、入力された色値と任意の係数(デフォルト0.5)を使用して、色を暗くした新しい色を生成します。色は赤、緑、青の各成分に分解され、ビット演算によって抽出されます。具体的には、赤は&0xFF、緑は8ビット右シフト後に&0xFF、青は16ビット右シフト後に&0xFFで取得します。その後、各成分に係数を掛けて減衰させ、uchar型にキャストしたうえで再構築し、左シフトとビットOR演算によって再び1つの色値として結合します。
次にdrawRectangleLabel関数を定義し、チャート上の矩形ラベルの描画および更新を管理します。まずObjectFindでオブジェクトの存在を確認し、存在しない場合はObjectCreateを用いてOBJ_RECTANGLE_LABEL型として作成します。作成に失敗した場合はPrintでエラーメッセージを出力しfalseを返します。既に存在する場合は、xとyの距離、サイズ、色、枠線タイプ(デフォルトはBORDER_FLAT)、背景表示(デフォルトtrue)などのプロパティを設定し、選択不可および操作不可に設定したうえでtrueを返します。
同様にdrawLabel関数ではテキストラベルの描画処理をおこないます。まずObjectFindで存在確認をおこない、存在しない場合はObjectCreateでOBJ_LABELとして生成します。生成失敗時にはエラーメッセージを出力しfalseを返します。その後、xとyの距離、表示テキスト、色、フォントサイズ(10)、フォント(Arial)を設定し、背景非表示、選択不可、操作不可に設定したうえで、正常終了時にtrueを返します。これらの基盤関数の実装後、初期化処理では新たに追加した平均ローソク足サイズ用バッファのバインドをおこなう必要があります。また、Canvasの初期化処理を実行し、リスク管理テーブル用ラベルのセットアップもここでおこないます。
//+------------------------------------------------------------------+ //| Initialize indicator | //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "Smart WaveTrend Crossover"); //--- Set short name PlotIndexSetInteger(1, PLOT_ARROW, 233); //--- Set buy arrow symbol PlotIndexSetInteger(1, PLOT_SHOW_DATA, signal_type == Triangles); //--- Set buy visibility PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, signal_buy_col); //--- Set buy color PlotIndexSetInteger(2, PLOT_ARROW, 234); //--- Set sell arrow symbol PlotIndexSetInteger(2, PLOT_SHOW_DATA, signal_type == Triangles); //--- Set sell visibility PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, signal_sell_col); //--- Set sell color SetIndexBuffer(23, avg_candle_size, INDICATOR_CALCULATIONS); //--- Bind avg candle size currentChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width currentChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height string canvas_name = "SWTC_Canvas"; //--- Set canvas name if (!obj_Canvas.CreateBitmapLabel(0, 0, canvas_name, 0, 0, currentChartWidth, currentChartHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create canvas Print("Failed to create canvas"); //--- Log failure return(INIT_FAILED); //--- Return failure } tpSlTableObjects[0] = objPrefix + "Table_Frame"; //--- Set table frame tpSlTableObjects[1] = objPrefix + "Table_Level"; //--- Set table level tpSlTableObjects[2] = objPrefix + "Table_Price"; //--- Set table price tpSlTableObjects[3] = objPrefix + "Table_TP1"; //--- Set TP1 label tpSlTableObjects[4] = objPrefix + "Table_TP1_Price"; //--- Set TP1 price tpSlTableObjects[5] = objPrefix + "Table_TP2"; //--- Set TP2 label tpSlTableObjects[6] = objPrefix + "Table_TP2_Price"; //--- Set TP2 price tpSlTableObjects[7] = objPrefix + "Table_TP3"; //--- Set TP3 label tpSlTableObjects[8] = objPrefix + "Table_TP3_Price"; //--- Set TP3 price tpSlTableObjects[9] = objPrefix + "Table_SL"; //--- Set SL label tpSlTableObjects[10] = objPrefix + "Table_SL_Price"; //--- Set SL price current_font_size = 10; //--- Initialize font size return(INIT_SUCCEEDED); //--- Return success }
OnInitイベントハンドラでは、インジケーターの追加プロパティを設定します。まずIndicatorSetStringを使用して短縮名を設定しますが、ここでは一貫性を持たせるために名称を変更しています。買いシグナルのプロットについては、これまで通りPlotIndexSetIntegerを用いて矢印コード233を設定します。ただし、シグナルタイプがTrianglesの場合はCanvas側でシグナルバブルを描画するため、このプロットは表示と非表示を切り替える形で制御し、ユーザー定義の買いシグナルカラーを適用します。同様に売りシグナルでは矢印コード234を設定し、同様の条件で表示制御をおこない、売り用カラーを適用します。また、平均ローソク足サイズ用バッファをインデックス23にバインドし、フォグおよびボックス拡張の計算に利用できるようにします。
Canvasを適切に初期化するために、ChartGetIntegerを使用して現在のチャートの寸法(幅と高さ)を取得します。Canvasオブジェクトに対して、Canvas名を定義し、サブウィンドウ、位置、サイズ、およびカラーフォーマットCOLOR_FORMAT_ARGB_NORMALIZEを指定して、その上にビットマップラベルを作成します。作成に失敗した場合は、Printを使用してエラーをログに記録し、INIT_FAILEDを返します。リスク管理表示用のテーブルオブジェクトについては、配列に対してプレフィックス付きの名前を割り当てて初期化します。結果として、フレーム本体、レベル表示用ラベル、価格表示用ラベル、そして各TPおよびSLに対応する個別要素を一元的に管理できるようになります。続いて、テキスト描画に使用するフォントサイズ変数を10で初期化します。最後に、設定が正常に完了したことを示すためINIT_SUCCEEDEDを返します。初期化後、次のような結果が表示されます。

画像から、Canvasが初期化され、描画の準備が整っていることがわかります。次におこなう必要があるのはCanvasオブジェクトの描画ですが、描画処理をより簡単で分かりやすくするために、ボックスと取引レベルの適切な価格を描画するためのヘルパー関数を定義し、それらを用いてテーブルに値を反映していきます。
//+------------------------------------------------------------------+ //| Draw right price label | //+------------------------------------------------------------------+ bool drawRightPrice(string objectName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID, int lineWidth = 1) { bool objectExists = (ObjectFind(0, objectName) >= 0); //--- Check if object exists if (!objectExists) { //--- Handle new object if (!ObjectCreate(0, objectName, OBJ_ARROW_RIGHT_PRICE, 0, lineTime, linePrice)) { //--- Create right price Print("Failed to create ", objectName); //--- Log failure return false; //--- Return failure } } else { //--- Handle existing object ObjectSetInteger(0, objectName, OBJPROP_TIME, 0, lineTime); //--- Set time ObjectSetDouble(0, objectName, OBJPROP_PRICE, 0, linePrice); //--- Set price } ObjectSetInteger(0, objectName, OBJPROP_COLOR, lineColor); //--- Set color ObjectSetInteger(0, objectName, OBJPROP_WIDTH, lineWidth); //--- Set width ObjectSetInteger(0, objectName, OBJPROP_STYLE, lineStyle); //--- Set style ObjectSetInteger(0, objectName, OBJPROP_FONTSIZE, 10); //--- Set font size ObjectSetString(0, objectName, OBJPROP_FONT, "Arial"); //--- Set font ObjectSetInteger(0, objectName, OBJPROP_BACK, false); //--- Disable background ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Update font sizes | //+------------------------------------------------------------------+ void UpdateFontSizes() { long scale = 0; //--- Initialize scale if (ChartGetInteger(0, CHART_SCALE, 0, scale)) { //--- Get chart scale current_font_size = (int)(8 + scale * 1.5); //--- Compute font size current_font_size = MathMax(8, MathMin(18, current_font_size)); //--- Clamp font size ChartRedraw(0); //--- Redraw chart } } //+------------------------------------------------------------------+ //| Convert bar width | //+------------------------------------------------------------------+ int BarWidth(int chartScale) { return (int)MathPow(2.0, chartScale); //--- Return bar width } //+------------------------------------------------------------------+ //| Convert shift to x | //+------------------------------------------------------------------+ int ShiftToX(int bar_index) { return (firstVisibleBarIndex - bar_index) * BarWidth(currentChartScale); //--- Return x position } //+------------------------------------------------------------------+ //| Convert price to y | //+------------------------------------------------------------------+ int PriceToY(double price) { if (maxPrice - minPrice == 0.0) return 0; //--- Handle zero range return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice)); //--- Return y position } //+------------------------------------------------------------------+ //| Draw box on canvas | //+------------------------------------------------------------------+ void DrawBoxOnCanvas(int x_left, int y_top, int x_right, int y_bottom, color fillColor, int fillTransp) { int x1 = MathMin(x_left, x_right); //--- Set min x int x2 = MathMax(x_left, x_right); //--- Set max x int y1 = MathMin(y_top, y_bottom); //--- Set min y int y2 = MathMax(y_top, y_bottom); //--- Set max y uchar alpha_fill = (uchar)(255 * (100 - fillTransp) / 100); //--- Compute fill alpha uint argb_fill = ColorToARGB(fillColor, alpha_fill); //--- Get fill ARGB obj_Canvas.FillRectangle(x1, y1, x2, y2, argb_fill); //--- Fill rectangle color borderColor = DarkenColor(fillColor, 0.7); //--- Get border color uint argb_border = ColorToARGB(borderColor, 255); //--- Get border ARGB obj_Canvas.LineAA(x1, y1, x2, y1, argb_border); //--- Draw top border obj_Canvas.LineAA(x1, y1 + 1, x2, y1 + 1, argb_border); //--- Draw top inner obj_Canvas.LineAA(x1, y2, x2, y2, argb_border); //--- Draw bottom border obj_Canvas.LineAA(x1, y2 - 1, x2, y2 - 1, argb_border); //--- Draw bottom inner obj_Canvas.LineAA(x1, y1, x1, y2, argb_border); //--- Draw left border obj_Canvas.LineAA(x1 + 1, y1, x1 + 1, y2, argb_border); //--- Draw left inner obj_Canvas.LineAA(x2, y1, x2, y2, argb_border); //--- Draw right border obj_Canvas.LineAA(x2 - 1, y1, x2 - 1, y2, argb_border); //--- Draw right inner }
まず、drawRightPrice関数を作成し、チャート上に右寄せの価格ラベルを描画または更新します。ObjectFindでオブジェクトの存在を確認し、存在しない場合はObjectCreateでOBJ_ARROW_RIGHT_PRICE型として作成します。作成に失敗した場合はPrintでエラーを出力しfalseを返します。既存オブジェクトの場合は時間と価格プロパティを調整し、その後、色、幅、スタイル(デフォルトはSTYLE_SOLID)、フォントサイズ10、フォントArialを設定し、背景、選択可否、選択状態を無効化したうえでChartRedrawにより再描画し、trueを返します。次に、UpdateFontSizes関数を定義し、テキストサイズを動的に調整します。ChartGetIntegerでチャートスケールを取得し、フォントサイズを8 + 1.5 × スケールとして計算し、MathMaxとMathMinで8から18の範囲に制限し、チャートを再描画します。
BarWidth関数では、チャートスケールに基づくバーのピクセル幅を計算し、MathPowを用いて2のスケール乗を求めて整数として返します。ShiftToX関数は、最初の可視バーからの差分にバー幅を掛けることで、バーインデックスをCanvas上のx座標に変換します。同様にPriceToY関数では価格をy座標に変換し、価格レンジが0の場合は0を返し、それ以外では最大値から最小値への比率をMathRoundで算出し、チャート高さでスケーリングします。
最後に、DrawBoxOnCanvas関数を実装し、Canvas上に塗りつぶし矩形と枠線を描画します。MathMinとMathMaxで最小と最大座標を決定し、透明度からアルファ値を算出し、ColorToARGBで色を変換してFillRectangleで塗りつぶします。その後、DarkenColorでより暗い枠線色を生成し、LineAAを用いて上下左右にアンチエイリアス付きの外枠および内枠ラインを描画します。AAはアンチエイリアスを意味し、線のギザギザを滑らかにするために使用しています。より理解を深めるために、以下の画像をご覧ください。

画像から分かるように、アンチエイリアスを採用した理由は線を滑らかにするためです。これで、これらの関数をインジケーターの計算およびビジュアル描画で使用していくことができます。まず、テーブル化された取引レベルをチャート上に維持したいため、新しいシグナルで明示的に変更されない限り、呼び出し間でそれらを静的に保持する必要があります。
//+------------------------------------------------------------------+ //| Calculate indicator values | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { static int last_dir = 0; //--- Last direction static double last_sl = 0.0; //--- Last SL static double last_tp1 = 0.0; //--- Last TP1 static double last_tp2 = 0.0; //--- Last TP2 static double last_tp3 = 0.0; //--- Last TP3 static datetime last_signal_time = 0; //--- Last signal time if (prev_calculated == 0) { //--- Handle initial calc //--- Existing initializations ArrayInitialize(avg_candle_size, EMPTY_VALUE); //--- Init avg candle ArrayInitialize(buyArrowBuf, EMPTY_VALUE); //--- Init buy arrows ArrayInitialize(sellArrowBuf, EMPTY_VALUE); //--- Init sell arrows ArrayResize(all_boxes, 0); //--- Clear boxes ArrayResize(all_signals, 0); //--- Clear signals last_dir = 0; //--- Reset direction last_sl = 0.0; //--- Reset SL last_tp1 = 0.0; //--- Reset TP1 last_tp2 = 0.0; //--- Reset TP2 last_tp3 = 0.0; //--- Reset TP3 last_signal_time = 0; //--- Reset signal time } }
OnCalculateイベントハンドラ内では、再計算間で状態を保持するためにstatic変数を宣言します。これには、直近のシグナル方向を保持する整数、最新のSLおよび3つのTPレベルを保持するdouble、そして最後のシグナル時刻を保持するdatetimeが含まれ、リスク管理表示の連続性を確保します。prev_calculatedが0の場合、すなわち初回計算または完全リセット時には、初期化処理を拡張します。ArrayInitializeを用いて平均ローソク足サイズバッファおよび矢印バッファをEMPTY_VALUEに設定し、ArrayResizeでボックス配列およびシグナル配列をサイズ0にして過去データをクリアし、すべてのstatic変数を0や0.0といった初期値にリセットしてクリーンな状態から開始します。Canvasの再描画を必要な場合のみに限定するため、以下のように再描画フラグを追加します。
bool new_signal_redraw = false; //--- New redraw flag
計算ループ内では、ボラティリティを扱うために平均ローソク足サイズの計算ロジックを組み込みます。
double sum_co = 0; //--- Init sum int cnt_co = 0; //--- Init count for (int k = 0; k < 50; k++) { //--- Loop candles if (i - k < 0) break; //--- Skip invalid sum_co += MathAbs(close[i - k] - open[i - k]); //--- Accumulate cnt_co++; //--- Increment } if (cnt_co > 0) avg_candle_size[i] = sum_co / cnt_co; //--- Set average else avg_candle_size[i] = MathAbs(close[i] - open[i]); //--- Set default
ループ内では、まずsum用のdouble変数を0で初期化し、count用の整数も0で初期化します。その後、現在のインデックスから過去方向へ最大50本のバーをループし、有効なバーに対してはcloseとopenの差の絶対値をMathAbsで算出してsumに加算し、countをインクリメントします。インデックスが負になる場合は途中でループを終了します。countが正の場合は、その合計値をcountで割ることで現在バーの平均ローソク足サイズを設定し、そうでない場合は現在バー単体の実体サイズをデフォルト値として設定します。
さらに、従来の矢印配置ロジックは、クロス時に即座に矢印を出す方式から、ボックス生成およびイベント駆動ロジックへ置き換える必要があります。具体的には、クロス発生時にレンジボックスを生成し、ブレイクアウトによってシグナルを確定させ、配列サイズを制限しつつ、条件に応じて矢印またはラベルを表示す構成にします。これにより、ブレイクアウトまで保持されるレンジボックスを導入し、方向性に基づいてフィルタリングしながら、複数の表示形式に対応した、よりコンテキストを持つ売買シグナルを実現します。
//--- BEFORE // buyArrowBuf[i] = EMPTY_VALUE; //--- Reset buy arrow // sellArrowBuf[i] = EMPTY_VALUE; //--- Reset sell arrow // if (signal_bull_cross[i] == 1 && (!use_trend_filter || trend_is_bull[i] == 1)) { //--- Check buy condition // buyArrowBuf[i] = low[i] - _Point * base_offset; //--- Place buy arrow // } // if (signal_bear_cross[i] == 1 && (!use_trend_filter || trend_is_bear[i] == 1)) { //--- Check sell condition // sellArrowBuf[i] = high[i] + _Point * base_offset; //--- Place sell arrow // } //--- AFTER double box_top = use_box_multiplier ? high[i] + avg_candle_size[i] * box_multiplier : high[i]; //--- Set top double box_bottom = use_box_multiplier ? low[i] - avg_candle_size[i] * box_multiplier : low[i]; //--- Set bottom if (signal_bull_cross[i] == 1 && (!use_trend_filter || trend_is_bull[i] == 1)) { //--- Check bull signal BoxInfo b; //--- Create box b.left_time = time[i]; //--- Set left b.right_time = 0; //--- Set right b.top = box_top; //--- Set top b.bottom = box_bottom; //--- Set bottom b.dir = 1; //--- Set dir ArrayResize(all_boxes, ArraySize(all_boxes) + 1); //--- Resize boxes all_boxes[ArraySize(all_boxes) - 1] = b; //--- Add box } if (signal_bear_cross[i] == 1 && (!use_trend_filter || trend_is_bear[i] == 1)) { //--- Check bear signal BoxInfo b; //--- Create box b.left_time = time[i]; //--- Set left b.right_time = 0; //--- Set right b.top = box_top; //--- Set top b.bottom = box_bottom; //--- Set bottom b.dir = -1; //--- Set dir ArrayResize(all_boxes, ArraySize(all_boxes) + 1); //--- Resize boxes all_boxes[ArraySize(all_boxes) - 1] = b; //--- Add box } bool buy_event = false; //--- Buy event flag bool sell_event = false; //--- Sell event flag for (int j = ArraySize(all_boxes) - 1; j >= 0; j--) { //--- Loop boxes if (all_boxes[j].right_time == 0) { //--- Check active if (close[i] > all_boxes[j].top) { //--- Check break up if (!show_only_matching || all_boxes[j].dir == 1) buy_event = true; //--- Set buy all_boxes[j].right_time = time[i]; //--- Close box } if (close[i] < all_boxes[j].bottom) { //--- Check break down if (!show_only_matching || all_boxes[j].dir == -1) sell_event = true; //--- Set sell all_boxes[j].right_time = time[i]; //--- Close box } } } while (ArraySize(all_boxes) > 500) { //--- Limit boxes bool removed = false; //--- Removed flag for (int j = 0; j < ArraySize(all_boxes); j++) { //--- Loop to remove if (all_boxes[j].right_time != 0) { //--- Check closed ArrayRemove(all_boxes, j, 1); //--- Remove box removed = true; //--- Set removed break; //--- Exit loop } } if (!removed) break; //--- No more to remove } if (signal_type == Triangles) { //--- Check triangles if (buy_event) { //--- Handle buy buyArrowBuf[i] = low[i] - _Point * base_offset; //--- Set arrow } if (sell_event) { //--- Handle sell sellArrowBuf[i] = high[i] + _Point * base_offset; //--- Set arrow } } else { //--- Handle labels if (buy_event) { //--- Handle buy SignalInfo s; //--- Create signal s.time = time[i]; //--- Set time s.dir = 1; //--- Set dir ArrayResize(all_signals, ArraySize(all_signals) + 1); //--- Resize signals all_signals[ArraySize(all_signals) - 1] = s; //--- Add signal new_signal_redraw = (i == rates_total - 1); //--- Set redraw } if (sell_event) { //--- Handle sell SignalInfo s; //--- Create signal s.time = time[i]; //--- Set time s.dir = -1; //--- Set dir ArrayResize(all_signals, ArraySize(all_signals) + 1); //--- Resize signals all_signals[ArraySize(all_signals) - 1] = s; //--- Add signal new_signal_redraw = (i == rates_total - 1); //--- Set redraw } } while (ArraySize(all_signals) > 500) { //--- Limit signals ArrayRemove(all_signals, 0, 1); //--- Remove oldest }
シグナルボックスの上限と下限は、基本的にバーの高値と安値を使用して決定しますが、ボックス拡張が有効な場合は、平均ローソク足サイズにボックス倍率を掛けた値を加減することで、価格レンジにバッファを持たせます。強気クロスオーバーが検出され、かつトレンドフィルタ条件を満たしている場合は、BoxInfo構造体をインスタンス化し、leftに現在時刻、rightに0(アクティブ状態を示す)、topとbottomに計算された価格、directionに1(強気)を設定します。その後、ArrayResize とArraySizeを使ってall_boxes配列のサイズを1つ拡張し、新しい構造体を末尾に追加します。弱気クロスオーバーの場合も同様に、directionを-1(弱気)として設定し、配列に追加します。
次に、買いイベントおよび売りイベント用のブールフラグをfalseで初期化し、all_boxes配列を末尾から逆順に走査します。各ボックスについて、rightが0のアクティブ状態であれば、現在の終値がtopを上回った場合に上方向ブレイクアウトと判定し、方向が一致しているか、または一致条件が無効であれば買いイベントフラグをtrueに設定し、rightに現在時刻を代入してボックスをクローズします。同様に、終値がbottomを下回った場合は下方向ブレイクアウトとして売りイベントを設定し、ボックスをクローズします。配列サイズの管理として、all_boxesが500要素を超えた場合は、先頭から走査して最初に見つかったクローズ済みボックスをArrayRemoveで削除します。削除に成功した場合はフラグを立ててループを抜け、削除できる要素がない場合は処理を終了します。
シグナルタイプがTrianglesの場合、買いイベント時には安値の下にoffset × _Point分だけずらした位置に買い矢印を配置し、売りイベント時には高値の上に同様に配置します。それ以外のラベルタイプの場合、買いイベント時にはSignalInfo構造体を生成し、timeに現在時刻、directionに1を設定してall_signals配列に追加し、最後のバーであれば再描画フラグを立てます。売りイベントについてもdirectionを-1として同様に処理します。最後に、all_signalsが500要素を超えた場合は、ArrayRemoveを用いて先頭の最も古い要素を削除します。これでCanvas描画の準備が整いました。まずはフォグ描画から開始し、処理のモジュール化のために専用関数としてロジックを実装していきます。
//+------------------------------------------------------------------+ //| Redraw canvas | //+------------------------------------------------------------------+ void Redraw(int rates_total) { if (currentChartWidth <= 0 || currentChartHeight <= 0) return; //--- Handle invalid size double h[], l[], c[], acs[], th[]; //--- Declare arrays datetime t[]; //--- Declare time if (CopyHigh(_Symbol, _Period, 0, rates_total, h) != rates_total) return; //--- Copy high if (CopyLow(_Symbol, _Period, 0, rates_total, l) != rates_total) return; //--- Copy low if (CopyClose(_Symbol, _Period, 0, rates_total, c) != rates_total) return; //--- Copy close if (CopyTime(_Symbol, _Period, 0, rates_total, t) != rates_total) return; //--- Copy time ArrayCopy(acs, avg_candle_size, 0, 0, rates_total); //--- Copy avg size ArrayCopy(th, trend_hist, 0, 0, rates_total); //--- Copy hist uint default_color = 0; //--- Default color obj_Canvas.Erase(default_color); //--- Erase canvas current_font_size = (int)(10 + currentChartScale * 1.5); //--- Compute font current_font_size = MathMax(10, MathMin(24, current_font_size)); //--- Clamp font if (show_fog) { //--- Check fog int total = visibleBarsCount; //--- Set total int previousX = -1; //--- Prev x double previous_hl2 = 0.0; //--- Prev hl2 double previous_offset = 0.0; //--- Prev offset int previous_dir = 0; //--- Prev dir color previous_fog_color = clrNONE; //--- Prev color for (int i = 0; i < total; i++) { //--- Loop visible int bar_index = firstVisibleBarIndex - i; //--- Compute index if (bar_index < 0 || bar_index >= rates_total) continue; //--- Skip invalid int x = ShiftToX(bar_index); //--- Get x if (x >= currentChartWidth) continue; //--- Skip offscreen int buffer_index = rates_total - 1 - bar_index; //--- Compute buffer double hl2 = (h[buffer_index] + l[buffer_index]) / 2.0; //--- Compute hl2 double offset_val = acs[buffer_index] * offset_mult; //--- Compute offset int dir = th[buffer_index] >= 0 ? -1 : 1; //--- Set dir color fog_color = th[buffer_index] >= 0 ? col_up : col_dn; //--- Set color if (previousX != -1 && x > previousX) { //--- Check previous double deltaX = x - previousX; //--- Compute delta int endColumn = MathMin(x, currentChartWidth - 1); //--- Set end for (int column = previousX + 1; column <= endColumn; column++) { //--- Loop columns double t_val = (column - previousX) / deltaX; //--- Compute t double interp_hl2 = previous_hl2 + t_val * (hl2 - previous_hl2); //--- Interp hl2 double interp_offset = previous_offset + t_val * (offset_val - previous_offset); //--- Interp offset int interp_dir = previous_dir; //--- Interp dir color interp_fog_color = previous_fog_color; //--- Interp color double full_offset = 6.0 * interp_offset; //--- Full offset double edge_price = interp_hl2 + interp_dir * full_offset; //--- Edge price int slow_y = PriceToY(interp_hl2); //--- Slow y int fast_y = PriceToY(edge_price); //--- Fast y int upperY = MathMin(slow_y, fast_y); //--- Upper y int lowerY = MathMax(slow_y, fast_y); //--- Lower y upperY = MathMax(0, upperY); //--- Clamp upper lowerY = MathMin(currentChartHeight - 1, lowerY); //--- Clamp lower double height_pixels = MathAbs(slow_y - fast_y); //--- Height if (height_pixels == 0.0) continue; //--- Skip zero double total_inc = transp_inc * 6.0; //--- Total inc for (int row = upperY; row <= lowerY; row++) { //--- Loop rows double distanceFromSlow_pixels = MathAbs(row - slow_y); //--- Distance double gradientFraction = distanceFromSlow_pixels / height_pixels; //--- Fraction double transp = base_transp + total_inc * gradientFraction; //--- Transp if (transp > 100.0) transp = 100.0; //--- Clamp transp uchar alpha = (uchar)(255 * (100 - transp) / 100.0); //--- Alpha uint argb = ColorToARGB(interp_fog_color, alpha); //--- ARGB obj_Canvas.PixelSet(column, row, argb); //--- Set pixel } } } previousX = x; //--- Update prev x previous_hl2 = hl2; //--- Update prev hl2 previous_offset = offset_val; //--- Update prev offset previous_dir = dir; //--- Update prev dir previous_fog_color = fog_color; //--- Update prev color } } obj_Canvas.Update(); //--- Update canvas }
Redraw関数では、まず現在のチャート幅または高さが正の値であるかを確認し、いずれかが0または負の場合は無効な処理を避けるために早期リターンします。次に、高値、安値、終値、平均ローソク足サイズ、トレンドヒストグラム、時間のローカル配列を宣言し、CopyHigh、CopyLow、CopyClose、CopyTimeを使用して先頭から全バー分のデータをコピーし、いずれかが期待件数と一致しない場合は終了します。平均ローソク足サイズおよびトレンドヒストグラムについては、ArrayCopyを用いてバッファからローカル配列へ転送し、描画に使用します。描画前にEraseメソッドでCanvasを0のデフォルト色でクリアし、再描画に備えます。フォントサイズは10 + 1.5 × チャートスケールで計算し、MathMaxとMathMinで10から24の範囲に制限します。
フォグ表示が有効な場合、ループ回数を可視バー数に設定し、前回のx座標、hl2価格、オフセット、方向、色の追跡用変数を初期化します。左から右へ各可視バーを処理し、バーインデックスを最初の可視バーからループカウンタを引いて求め、範囲外や無効インデックスはスキップします。ShiftToXでx座標を取得し、チャート幅を超える場合はスキップします。バッファインデックスは総バー数から1を引き、さらにバーインデックスを引いて求め、hl2は高値と安値の中間値、オフセットは平均ローソク足サイズに倍率を掛けて算出し、方向はトレンドヒストグラムが0以上なら-1、それ以外は1とします。フォグ色はヒストグラムの符号に応じてユーザー定義の上昇色または下降色を使用します。
前回のxが存在し、かつ現在のxがそれより大きい場合、ピクセル差分を求め、終了列を現在のxまたはチャート幅-1の小さい方とします。その範囲の各列について、差分に対する相対位置から補間係数tを求め、hl2とオフセットを前回値と現在値の間で線形補間し、方向と色は前回値を使用します。補間オフセットに6を掛けた値をフルオフセットとし、方向に応じてhl2に加減してエッジ価格を算出します。PriceToYでhl2とエッジをy座標へ変換し、上端と下端をそれぞれ最小と最大として求め、MathMaxとMathMinで0からチャート高さ-1の範囲に制限します。y差が0の場合はスキップし、透明度増分に6を掛けた値を総増分とします。
上端から下端まで各行について、hl2位置からのピクセル距離を求め、高さに対する比率としてグラデーション係数を算出します。透明度は基準値に総増分と係数を掛けて加算し、最大100に制限します。アルファ値は255 × (100 - 透明度) / 100としてucharに変換し、ColorToARGBで色とアルファを合成します。PixelSetで該当列と行のピクセルを設定します。各バー処理後に前回値を現在値で更新します。最後にUpdateでCanvas表示を更新し、描画内容を反映します。進行状況を明確にするため、この関数は計算イベントハンドラ内の主要な更新処理の後に呼び出し、新しい情報に基づいて再描画します。
bool hasChartChanged = false; //--- Change flag int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height int newChartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get new scale int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first bar int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get new min double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get new max if (newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) { //--- Check size change obj_Canvas.Resize(newChartWidth, newChartHeight); //--- Resize canvas currentChartWidth = newChartWidth; //--- Update width currentChartHeight = newChartHeight; //--- Update height hasChartChanged = true; //--- Set changed } if (newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount || newMinPrice != minPrice || newMaxPrice != maxPrice) { //--- Check other changes currentChartScale = newChartScale; //--- Update scale firstVisibleBarIndex = newFirstVisibleBar; //--- Update first bar visibleBarsCount = newVisibleBars; //--- Update visible minPrice = newMinPrice; //--- Update min maxPrice = newMaxPrice; //--- Update max hasChartChanged = true; //--- Set changed } datetime currentTime = TimeCurrent(); //--- Get current time if (hasChartChanged || rates_total > prev_calculated || new_signal_redraw) { //--- Check redraw Redraw(rates_total); //--- Call redraw lastRedrawTime = currentTime; //--- Update time } ChartRedraw(0); //--- Redraw chart
OnCalculateイベントハンドラでは、まずチャートの変更を追跡するためのブールフラグを初期化します。その後、ChartGetIntegerを使用して更新後のチャートプロパティを取得します。具体的には、CHART_WIDTH_IN_PIXELS とCHART_HEIGHT_IN_PIXELSで新しい幅と高さ、CHART_SCALEでスケール、CHART_FIRST_VISIBLE_BARで最初の可視バー、CHART_VISIBLE_BARSで可視バー数を取得します。また、ChartGetDoubleを用いてCHART_PRICE_MINおよびCHART_PRICE_MAXから最小価格と最大価格を取得します。新しい幅または高さが現在の値と異なる場合、新しい寸法を渡してResizeメソッドを使用してCanvasオブジェクトのサイズを変更し、グローバルな幅と高さの変数を更新し、変更フラグをtrueに設定します。
同様に、スケール、最初の可視バー、可視バー数、最小価格、最大価格のいずれかが変更された場合も、対応するグローバル変数を更新し、フラグをtrueに設定します。次に、TimeCurrentで現在のサーバー時刻を取得し、フラグがtrueであるか、rates_totalとprev_calculatedを比較して新しいバーが追加された場合、または新しいシグナルにより再描画が必要な場合のいずれかに該当すれば、Redraw関数をrates_totalとともに呼び出し、最後の再描画タイムスタンプを更新します。最後に、ChartRedrawを呼び出してチャートを強制的に更新し、すべての変更が画面に反映されるようにします。チャートに適用すると、以下のような結果が表示されます。

画像を見ると、ローソク足の範囲に基づいてフォグが正しく設定されていることが分かります。次にボックスを描画する必要があります。この処理は同じ再描画関数の中に組み込み、条件に関係なく常に実行する形にします。
for (int j = 0; j < ArraySize(all_boxes); j++) { //--- Loop boxes int left_bar = iBarShift(_Symbol, _Period, all_boxes[j].left_time); //--- Get left bar int x_left = ShiftToX(left_bar); //--- Get left x int x_right; //--- Declare right x if (all_boxes[j].right_time == 0) { //--- Check active x_right = currentChartWidth - 1; //--- Set to end } else { //--- Handle closed int right_bar = iBarShift(_Symbol, _Period, all_boxes[j].right_time); //--- Get right bar x_right = ShiftToX(right_bar); //--- Get right x } int y_top = PriceToY(all_boxes[j].top); //--- Get top y int y_bottom = PriceToY(all_boxes[j].bottom); //--- Get bottom y color fill_col = all_boxes[j].dir == 1 ? box_bull_fill : box_bear_fill; //--- Set fill DrawBoxOnCanvas(x_left, y_top, x_right, y_bottom, fill_col, box_fill_transp); //--- Draw box }
ボックスを描画するために、まずall_boxes配列をループします。配列の総数はArraySizeを使用して取得し、インデックス0から末尾まで処理をおこないます。各ボックスについて、まず左側のバーインデックスを取得します。iBarShiftに銘柄、時間足、ボックスの左時間を渡し、タイムスタンプをバー位置に変換します。その後、ShiftToXを用いてCanvas上の左側X座標を計算します。
次に右側のX座標用の変数を用意します。もしボックスの右時間が0であれば(つまりまだ有効な状態であれば)、右端まで伸ばすために右Xをチャート幅から1引いた値に設定します。一方、ボックスがクローズされている場合は、同様にiBarShiftを使って右側のバーインデックスを取得し、ShiftToXでX座標に変換します。さらに、ボックスの上限価格と下限価格をPriceToYを使ってY座標に変換します。方向に応じて塗りつぶし色を選択し、方向が1の場合は強気の色、-1の場合は弱気の色を使用します。最後に、計算されたXとYの座標、選択した色、および透明度を引数としてDrawBoxOnCanvasを呼び出し、Canvas上にボックスを描画します。次のような結果が得られます。

ボックスが正しく描画されていることが確認できます。次に残る最も重要な部分は、選択された場合にのみシグナルバブルを条件付きで描画する処理です。これを実現するために、次のロジックを使用します。
if (signal_type == Labels_Buy_Sell) { //--- Check labels for (int j = 0; j < ArraySize(all_signals); j++) { //--- Loop signals int bar = iBarShift(_Symbol, _Period, all_signals[j].time); //--- Get bar if (bar > firstVisibleBarIndex || bar < firstVisibleBarIndex - visibleBarsCount) continue; //--- Skip off view int x = ShiftToX(bar); //--- Get x int buffer_index = rates_total - 1 - bar; //--- Get buffer double price = (all_signals[j].dir == 1) ? l[buffer_index] : h[buffer_index]; //--- Set price int y = PriceToY(price); //--- Get y string text = (all_signals[j].dir == 1) ? "BUY" : "SELL"; //--- Set text color bg_col = (all_signals[j].dir == 1) ? signal_buy_col : signal_sell_col; //--- Set bg color border_col = DarkenColor(bg_col, 0.5); //--- Set border color text_col = clrWhite; //--- Set text color obj_Canvas.FontSet("Arial Bold", (uint)current_font_size, FW_BOLD); //--- Set font int text_width = obj_Canvas.TextWidth(text); //--- Get width int text_height = obj_Canvas.TextHeight(text); //--- Get height int padding_width = 6 + current_font_size / 3; //--- Compute width pad int padding_height = 4 + current_font_size / 4; //--- Compute height pad int rect_width = text_width + padding_width; //--- Set rect width int rect_height = text_height + padding_height; //--- Set rect height int label_offset = base_offset + currentChartScale; //--- Set offset int tri_base = 6 + currentChartScale * 2; //--- Set tri base tri_base = (tri_base / 2) * 2; //--- Ensure even int tri_height = (int)(tri_base * 0.5); //--- Set tri height int x_rect = x - rect_width / 2; //--- Set rect x int y_rect = (all_signals[j].dir == 1) ? y + label_offset : y - rect_height - label_offset; //--- Set rect y uchar alpha = (uchar)(255 * (100 - 20) / 100); //--- Set alpha uint argb_fill = ColorToARGB(bg_col, alpha); //--- Get fill obj_Canvas.FillRectangle(x_rect, y_rect, x_rect + rect_width, y_rect + rect_height, argb_fill); //--- Fill rect uint argb_border = ColorToARGB(border_col, 255); //--- Get border obj_Canvas.LineAA(x_rect, y_rect, x_rect + rect_width, y_rect, argb_border); //--- Draw top obj_Canvas.LineAA(x_rect + rect_width, y_rect, x_rect + rect_width, y_rect + rect_height, argb_border); //--- Draw right obj_Canvas.LineAA(x_rect + rect_width, y_rect + rect_height, x_rect, y_rect + rect_height, argb_border); //--- Draw bottom obj_Canvas.LineAA(x_rect, y_rect + rect_height, x_rect, y_rect, argb_border); //--- Draw left int x_center = x_rect + rect_width / 2; //--- Set center if (all_signals[j].dir == 1) { //--- Handle buy int tri_left = x_center - tri_base / 2; //--- Set left int tri_right = x_center + tri_base / 2; //--- Set right int tri_tip_y = y_rect - tri_height; //--- Set tip obj_Canvas.FillTriangle(tri_left, y_rect, tri_right, y_rect, x_center, tri_tip_y, argb_fill); //--- Fill tri obj_Canvas.LineAA(tri_left, y_rect, x_center, tri_tip_y, argb_border); //--- Draw left slant obj_Canvas.LineAA(tri_right, y_rect, x_center, tri_tip_y, argb_border); //--- Draw right slant } else { //--- Handle sell int tri_bottom_y = y_rect + rect_height; //--- Set bottom int tri_left = x_center - tri_base / 2; //--- Set left int tri_right = x_center + tri_base / 2; //--- Set right int tri_tip_y = tri_bottom_y + tri_height; //--- Set tip obj_Canvas.FillTriangle(tri_left, tri_bottom_y, tri_right, tri_bottom_y, x_center, tri_tip_y, argb_fill); //--- Fill tri obj_Canvas.LineAA(tri_left, tri_bottom_y, x_center, tri_tip_y, argb_border); //--- Draw left slant obj_Canvas.LineAA(tri_right, tri_bottom_y, x_center, tri_tip_y, argb_border); //--- Draw right slant } int text_x = x_rect + rect_width / 2; //--- Set text x int text_y = y_rect + rect_height / 2; //--- Set text y uint argb_text = ColorToARGB(text_col, 255); //--- Get text ARGB obj_Canvas.TextOut(text_x, text_y, text, argb_text, TA_CENTER | TA_VCENTER); //--- Draw text } }
まず、シグナルタイプがLabels_Buy_Sellであることを確認し、テキストバブルを描画する条件を満たしている場合のみ処理をおこないます。その後、all_signals配列をArraySizeを用いて総数分ループします。各シグナルについて、まずiBarShiftを使用して銘柄と時間足を渡し、タイムスタンプをバーインデックスに変換します。その際、バーが表示範囲外にある場合はスキップするため、最初の表示インデックスと表示バー数を比較して判定します。次にShiftToXを使ってX座標を計算し、バッファインデックスを総レート数 - 1 - バーとして導出します。参照価格は、買いシグナル(direction 1)の場合はロー価格、売りシグナルの場合はハイ価格とし、それをPriceToYでY座標に変換します。ラベルテキストは方向に応じてBUYまたはSELLとし、背景色はユーザー入力に基づいて選択します。枠線色はDarkenColorを使用して背景色を0.5の係数で暗くして生成し、テキスト色は白に設定します。
CanvasのフォントはFontSetを使ってArial Boldに設定し、現在のフォントサイズと太字フラグを適用します。TextWidthとTextHeightで文字サイズを測定し、フォントサイズに基づいてパディングを算出します。これによりテキストサイズにパディングを加えた矩形サイズを決定します。ラベルのオフセットは基本オフセットとチャートスケールを組み合わせて設定し、三角形の底辺は6 + スケール×2とし、整数除算と乗算により偶数化します。三角形の高さは底辺の半分を整数として設定します。矩形のX位置は、シグナルXから幅の半分を引くことで中央揃えにし、Y位置は方向に応じて上下にオフセットします。塗りつぶしアルファ値はuchar(255 × (100 - 20) / 100)として80%の透明度に設定し、ColorToARGBでARGB色を取得し、FillRectangleで矩形を塗りつぶします。境界線にはARGB値を取得し、長方形の上、右、下、左の辺にLineAAを使用してアンチエイリアス処理された線を描画します。
買いシグナルの場合、三角形は矩形の下側に配置し、左右の点は中心から底辺の半分だけオフセットします。頂点は高さ分だけ上に配置します。FillTriangleで塗りつぶし、その後LineAAで斜辺を描画します。この方法が採用された理由については既に理解している通りです。標準的な方法ではなく、この方式を選んでいる意図があります。売りシグナルの場合、三角形は矩形の上側ではなく下側を基準に配置され、同様に左右点を設定し、頂点は高さ分だけ下に配置されます。塗りつぶして、斜辺を描画します。最後に、テキストのXとYを矩形内で中央揃えにし、テキストカラーをARGBに変換した上で、TextOutを使用して水平と垂直中央揃えのフラグ付きで描画します。コンパイルすると、次の結果が得られます。

シグナルがバブルとして表示されていることが確認できます。次におこなうべきは、TPおよびSLのレベルを計算することです。
if (showTPSL && (buy_event || sell_event) && i == rates_total - 1) { //--- Check TP/SL int lastSignal = buy_event ? 1 : -1; //--- Set signal double lastClose = close[i]; //--- Set close double sum_range = 0; //--- Init sum int cnt_range = 0; //--- Init count for (int k = 0; k < tp_sl_length; k++) { //--- Loop range if (i - k < 0) break; //--- Skip invalid sum_range += high[i - k] - low[i - k]; //--- Accumulate cnt_range++; //--- Increment } double avgRange = (cnt_range > 0) ? sum_range / cnt_range : 0; //--- Compute avg double sl_val = (lastSignal == 1) ? (tpSlMode == Candle_Multiplier ? lastClose - avgRange * slMultiplier : lastClose * (1 - slPercent / 100)) : (tpSlMode == Candle_Multiplier ? lastClose + avgRange * slMultiplier : lastClose * (1 + slPercent / 100)); //--- Compute SL double tp1_val = (lastSignal == 1) ? (tpSlMode == Candle_Multiplier ? lastClose + avgRange * tp1Multiplier : lastClose * (1 + tp1Percent / 100)) : (tpSlMode == Candle_Multiplier ? lastClose - avgRange * tp1Multiplier : lastClose * (1 - tp1Percent / 100)); //--- Compute TP1 double tp2_val = (lastSignal == 1) ? (tpSlMode == Candle_Multiplier ? lastClose + avgRange * tp2Multiplier : lastClose * (1 + tp2Percent / 100)) : (tpSlMode == Candle_Multiplier ? lastClose - avgRange * tp2Multiplier : lastClose * (1 - tp2Percent / 100)); //--- Compute TP2 double tp3_val = (lastSignal == 1) ? (tpSlMode == Candle_Multiplier ? lastClose + avgRange * tp3Multiplier : lastClose * (1 + tp3Percent / 100)) : (tpSlMode == Candle_Multiplier ? lastClose - avgRange * tp3Multiplier : lastClose * (1 - tp3Percent / 100)); //--- Compute TP3 last_dir = lastSignal; //--- Update dir last_sl = sl_val; //--- Update SL last_tp1 = tp1_val; //--- Update TP1 last_tp2 = tp2_val; //--- Update TP2 last_tp3 = tp3_val; //--- Update TP3 last_signal_time = time[i]; //--- Update time new_signal_redraw = true; //--- Set redraw } if (i == rates_total - 1) { //--- Check last bar static bool last_buy = false; //--- Last buy static bool last_sell = false; //--- Last sell if (buy_event && !last_buy) { //--- Handle new buy Alert("WaveTrend BUY " + _Symbol + " @" + DoubleToString(close[i], _Digits)); //--- Alert buy last_buy = true; //--- Set last buy new_signal_redraw = true; //--- Set redraw } else last_buy = buy_event; //--- Update last buy if (sell_event && !last_sell) { //--- Handle new sell Alert("WaveTrend SELL " + _Symbol + " @" + DoubleToString(close[i], _Digits)); //--- Alert sell last_sell = true; //--- Set last sell new_signal_redraw = true; //--- Set redraw } else last_sell = sell_event; //--- Update last sell }
まず、TP/SLの表示が有効になっていること、買いまたは売りイベントが発生していること、そして現在がレート配列の最終バーであることを確認します。これらの条件がすべて満たされた場合、シグナル方向を(買いなら1、売りなら-1)として決定し、現在の終値を取得します。その後、sumとcountを0で初期化し、tp_sl_lengthまでの過去バーをループします。このとき、無効なインデックスはスキップし、各バーの高値−安値レンジをsumに加算し、有効なバーごとにcountをインクリメントします。
ループ終了後、countが正であればsum / countとして平均レンジを計算し、そうでなければ0とします。シグナル方向と選択されたtpSlMode(Candle_MultiplierまたはPercentage列挙型)に応じてストップロス値を計算します。買いの場合はマルチプライヤーモードではclose −(平均レンジ × slMultiplier)、パーセンテージモードではclose ×(1 − slPercent / 100)として算出し、売りの場合はその逆でclose +(平均レンジ × slMultiplier)、またはclose ×(1 + slPercent / 100)として計算します。同様に、3つのTP値についてもそれぞれ対応する倍率またはパーセンテージを用いて計算し、方向に応じて加算と減算を切り替えます。
その後、staticの最終シグナル方向、SLおよびTP、およびシグナル時刻を更新し、描画更新のためにredrawフラグをtrueに設定します。さらに、現在が最終バーである場合にはstaticなブール値を用いて直前の買いと売り状態を管理します。新規の買いシグナルで未フラグのケースでは、「WaveTrend BUY」に銘柄、"@"、およびDoubleToStringにより銘柄桁数でフォーマットされた終値を連結したメッセージでAlertを発行し、lastBuyをtrueに設定し、再描画を有効化します。それ以外の場合はlastBuyフラグを更新します。売りイベントも同様に処理し、「WaveTrend SELL」のアラートを発行してlastSellを更新し、新規イベント時にはredrawを有効化します。これにより、シグナル発生時にアラートが出力されるようになります。

チャート上のレベルを視覚化するために、以下のロジックを採用します。
if (showTPSL) { //--- Check TP/SL if (last_dir == 0) { //--- Handle no dir ObjectDelete(0, slLine); //--- Delete SL ObjectDelete(0, tp1Line); //--- Delete TP1 ObjectDelete(0, tp2Line); //--- Delete TP2 ObjectDelete(0, tp3Line); //--- Delete TP3 for (int i = 0; i < ArraySize(tpSlTableObjects); i++) { //--- Loop table ObjectDelete(0, tpSlTableObjects[i]); //--- Delete object } } else { //--- Handle dir datetime extension_time = last_signal_time; //--- Set time color tp_color = (last_dir == 1) ? col_up : col_dn; //--- Set TP color color sl_color = (last_dir == 1) ? col_dn : col_up; //--- Set SL color drawRightPrice(slLine, extension_time, last_sl, sl_color, STYLE_SOLID, 2); //--- Draw SL drawRightPrice(tp1Line, extension_time, last_tp1, tp_color, STYLE_SOLID, 1); //--- Draw TP1 drawRightPrice(tp2Line, extension_time, last_tp2, tp_color, STYLE_SOLID, 1); //--- Draw TP2 drawRightPrice(tp3Line, extension_time, last_tp3, tp_color, STYLE_SOLID, 1); //--- Draw TP3 currentChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Update width drawRectangleLabel(tpSlTableObjects[0], currentChartWidth - 150, 20, 120, 120, clrGray, BORDER_FLAT, true); //--- Draw frame drawLabel(tpSlTableObjects[1], currentChartWidth - 140, 30, "Level", clrBlack); //--- Draw level drawLabel(tpSlTableObjects[2], currentChartWidth - 80, 30, "Price", clrBlack); //--- Draw price drawLabel(tpSlTableObjects[3], currentChartWidth - 140, 50, "TP1", tp_color); //--- Draw TP1 drawLabel(tpSlTableObjects[4], currentChartWidth - 80, 50, DoubleToString(last_tp1, _Digits), tp_color); //--- Draw TP1 price drawLabel(tpSlTableObjects[5], currentChartWidth - 140, 70, "TP2", tp_color); //--- Draw TP2 drawLabel(tpSlTableObjects[6], currentChartWidth - 80, 70, DoubleToString(last_tp2, _Digits), tp_color); //--- Draw TP2 price drawLabel(tpSlTableObjects[7], currentChartWidth - 140, 90, "TP3", tp_color); //--- Draw TP3 drawLabel(tpSlTableObjects[8], currentChartWidth - 80, 90, DoubleToString(last_tp3, _Digits), tp_color); //--- Draw TP3 price drawLabel(tpSlTableObjects[9], currentChartWidth - 140, 110, "SL", sl_color); //--- Draw SL drawLabel(tpSlTableObjects[10], currentChartWidth - 80, 110, DoubleToString(last_sl, _Digits), sl_color); //--- Draw SL price } }
TP/SLの表示が有効かどうかを確認し、有効であればさらに最後のシグナル方向が0(アクティブなシグナルなし)であるかを判定します。この場合、関連するオブジェクトをすべて削除するため、TPおよびSLのラインに対してObjectDeleteを呼び出し、さらにテーブルオブジェクトの配列をArraySizeでループし、各要素を個別に削除します。一方、アクティブな方向が存在する場合は、まず拡張時間として最後のシグナル時刻を設定します。次にTPの色を、買いの場合は上昇カラー、売りの場合は下降カラーに設定し、ストップロスの色はその逆に設定します。その後、drawRightPriceを呼び出し、SLラインを太さ2の実線で描画し、各TPラインも太さの実線で、それぞれ拡張時間上に対応する価格と色で描画します。
続いて、チャート幅をChartGetIntegerCHART_WIDTH_IN_PIXELSで取得し更新します。その後、右端から150ピクセル、Y座標20の位置にサイズ120×120でテーブルフレームをdrawRectangleLabelで描画し、グレー色、フラットボーダー、背景有効の設定とします。さらにdrawLabelを用いてラベルを配置します。ヘッダとしてLevelPriceを黒色で描画し、各TPおよびSLについてもTP1などのラベルと、DoubleToStringで銘柄桁数にフォーマットした価格を表示し、それぞれ適切な色で右端からのオフセット位置に配置します。この部分は必要に応じて調整可能です。コンパイル後、以下のようにレベルが描画されます。

Canvas上でレベルが正しく描画されていることが確認できます。次に残るのは、チャート変更時にもシームレスに更新されるようにする処理です。そのためにOnChartEventイベントハンドラを使用し、チャートの変更を検知します。
//+------------------------------------------------------------------+ //| Handle chart event | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_CHART_CHANGE) { //--- Check change Redraw(Bars(_Symbol, _Period)); //--- Redraw UpdateFontSizes(); //--- Update fonts } }
OnChartEventイベントハンドラでは、イベントID、long値、double値、string値といった受信パラメータを処理し、ユーザーによるチャート操作を検知して反応します。IDがCHARTEVENT_CHART_CHANGEと一致する場合、これはズーム、パン、リサイズなどのチャート変更を示すため、更新処理をトリガーします。このとき、現在の銘柄と時間足を用いてBarsから取得した総バー数を引数にRedrawを呼び出し、さらにUpdateFontSizesを実行してテキスト要素のフォントサイズを再計算し、視認性を最適化します。また、インジケーターがチャートから削除される際には、チャート上に生成されたオブジェクトをすべて削除する必要があります。
//+------------------------------------------------------------------+ //| Deinitialize indicator | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { obj_Canvas.Destroy(); //--- Destroy canvas ArrayResize(all_boxes, 0); //--- Clear boxes ArrayResize(all_signals, 0); //--- Clear signals ObjectDelete(0, slLine); //--- Delete SL line ObjectDelete(0, tp1Line); //--- Delete TP1 line ObjectDelete(0, tp2Line); //--- Delete TP2 line ObjectDelete(0, tp3Line); //--- Delete TP3 line for (int i = 0; i < ArraySize(tpSlTableObjects); i++) { //--- Loop through table objects ObjectDelete(0, tpSlTableObjects[i]); //--- Delete object } ChartRedraw(0); //--- Redraw chart }
OnDeinitイベントハンドラでは、インジケーターがチャートから削除される際にリソースをクリーンアップします。まず、CanvasオブジェクトをDestroyメソッドで破棄し、描画要素を解放します。次に、all_boxesとall_signals配列をArrayResizeによりサイズ0へリサイズし、保持しているデータをクリアしてメモリを解放します。続いて、TPおよびSLの各ラインオブジェクトをObjectDeleteにより削除します。その後、テーブルオブジェクト配列についてもArraySizeを基準にループし、各要素をObjectDeleteで削除することで、リスク管理表示を完全に除去します。最後にChartRedrawを呼び出してチャートを更新し、すべての削除内容を反映させます。これで一連のクリーンアップ処理は完了です。 残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

結論
WaveTrend CrossoverインジケーターをMQL5上で強化し、Canvasベースの描画によるフォグ状グラデーション、ブレイクアウトを検知するシグナルボックス、視覚的なアラートとしてのカスタマイズ可能な買いと売りのバブルまたは三角形、さらに動的なTPおよびSLによる統合的なリスク管理を実装しました。このアップグレードでは、相場環境を示すフォグ状グラデーションといった高度な視覚表現に加え、トレンドフィルタリング、ボックスの延長設定、そしてローソク足倍率またはパーセンテージによる計算オプションが組み込まれています。これらはラインやテーブルを用いて表示されます。この構成により、モメンタムの転換、トレンド分析、リスク評価をより直感的に把握できる没入型ツールが提供されます。この強化されたWaveTrend Crossoverインジケーターによって、視覚的機能とリスク管理機能を活用し、より深い取引へのインサイトを得ることが可能になります。今後の取引の最適化にも対応できる設計です。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20815
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
Python-MetaTrader 5ストラテジーテスター(第3回):MetaTrader 5風の取引操作 — 処理と管理
ラリー・ウィリアムズの『市場の秘密』(第6回):市場変動を利用したボラティリティブレイクアウトの測定
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
プライスアクション分析ツールキットの開発(第54回):EMAと平滑化された価格変動によるトレンドのフィルタリング
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索