MQL5でカスタムインジケーターを作成する(第1回):Canvasグラデーションを使用したピボットベースのトレンドインジケーターの構築
はじめに
この記事では、高速/低速ピボットラインを計算し、方向矢印でトレンドを検出し、チャート上でピボットラインを前方に延長し、読みやすさを向上させるために、強気または弱気の領域を強調表示するオプションのCanvasグラデーションを提供する、MetaQuotes Language 5 (MQL5)でピボットベースのトレンドインジケーターを開発します。この記事で取り上げるトピックは以下の通りです。
最終的には、柔軟な視覚設定を備えた、ピボットトレンド検出用の完全に機能するMQL5インジケーターが完成します。開発を始めましょう。
ピボットベースのトレンドインジケーターフレームワークの理解
Pivot Trend Detectorインジケーターは、定義された期間における高値/安値の範囲に基づいて高速ピボットラインと低速ピボットラインを使用し、トレンドの方向と潜在的な反転を特定するテクニカルツールです。価格データを平滑化しながら、色分けされたラインと矢印によって変化を強調表示します。これは主に3本の線で構成されています。1本目は、主要なトレンドの基準線(価格の位置に基づいて上昇または下降を示す)として機能する緩やかな線、2本目は、トレンドの転換時に色が変わる速い点線、そして3本目は、価格が両方の線をクロスしたときに新しいトレンドの開始を示す矢印です。
この設定は、モメンタムの変化を捉えるのに役立ち、低速ラインはサポート/レジスタンスを提供し、高速ラインは早期のシグナルを提供し、期間調整によってボラティリティに適応します。実際には、このインジケーターは、価格が低速ライン(上向きの色で描画)より上にある場合は上昇トレンド、価格が低速ラインより下にある場合(下向きの色で描画)は下降トレンドであることを示すことで、トレンドフォローを支援します。矢印はクロスした時点でのエントリーポイントを示し、オプションの延長線は将来の予測のためにラインから延びています。この動的な塗りつぶしは、グラデーションの不透明度によってトレンドの強さを可視化し、ゆっくりとした変化から速い変化へとフェードインすることで、直感的に領域を強調表示します。
インジケーターのアーキテクチャは、入力パラメータ、インジケーターバッファ、およびグラフィカルプロパティという、明確な役割分担に基づいて構築します。まず、インジケーターの動作を決める主要な入力項目、例えば高速/低速期間、色、不透明度、矢印コード、拡張機能などを定義することから始めます。次に、緩やかな上昇/下降を示す線、色付きの高速ライン、色付きのトレンド矢印、およびトレンド/緩やかな値の内部計算を格納するために、8つのバッファを割り当てます。これらのバッファはグラフィカルなプロットにリンクされ、タイプ(線/カラー線/矢印)、色、幅、シフトなどのプロパティはMQL5の組み込み関数を使用して設定されます。さらに、Canvasクラスを使用して線間のスペースをグラデーションで塗りつぶすことで、インジケーターが市場の変動に動的に適応するようにします。まとめると、最終的なイメージは以下のとおりです。

MQL5での実装
MQL5でインジケーターを作成するには、まずMetaEditorを開き、ナビゲータに移動して、インジケーターフォルダを見つけ、[新規]タブをクリックして、表示される手順に従ってファイルを作成します。作成後、コーディング環境においてインジケーターのプロパティと設定を定義します。たとえばバッファの数、プロット数、さらに各ラインの個別プロパティとして色、幅、ラベルです。
//+------------------------------------------------------------------+ //| 1. Pivot Trend Detector.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property indicator_chart_window #property indicator_buffers 8 #property indicator_plots 4 #property indicator_label1 "PTD slow line up" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 #property indicator_label2 "PTD slow line down" #property indicator_type2 DRAW_LINE #property indicator_color2 clrCrimson #property indicator_width2 2 #property indicator_label3 "PTD fast line" #property indicator_type3 DRAW_COLOR_LINE #property indicator_color3 clrDodgerBlue,clrCrimson #property indicator_style3 STYLE_DOT #property indicator_label4 "PTD trend start" #property indicator_type4 DRAW_COLOR_ARROW #property indicator_color4 clrDodgerBlue,clrCrimson #property indicator_width4 2
実装は、プロパティディレクティブを使用してインジケーターのメタデータを定義することから始めます。具体的には、「indicator_chart_window」でメインチャートウィンドウに描画するように指定し、indicator_buffersで8つのバッファを割り当て、indicator_plotsで4つのプロットを設定します。最初のチャートには「PTD slow line up」というラベルを付け、タイプをDRAW_LINE、色をドジャーブルー、幅を2に設定します。2番目のチャートには「PTD slow line down」というラベルが付けられ、タイプは線、色は深紅、幅は2にします。3番目のラベルは「PTD fast line」、タイプはカラーライン、色はドジャーブルーとクリムゾン、スタイルは点線です。4番目のラベルは「PTD trend start」で、タイプはカラー矢印、色はドジャーブルーとクリムゾン、幅は2です。これにより、緩やかな上昇/下降線、色が変わる高速ライン、トレンド開始矢印などの視覚的な構造が確立されます。次に、プログラムで使用する入力パラメータとグローバル変数を定義します。
#include <Canvas/Canvas.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CCanvas obj_Canvas; //--- Canvas object //--- input parameters input int fastPeriod = 5; // Fast period input int slowPeriod = 10; // Slow period input color upColor = clrDodgerBlue; // Up trend color input color downColor = clrCrimson; // Down trend color input int fillOpacity = 128; // Fill opacity (0-255) input int arrowCode = 77; // Arrow code for trend start input bool showExtensions = true; // Show line extensions input bool enableFilling = true; // Enable canvas fill (disable for speed) input int extendBars = 1; // Extension bars to protrude lines/fill //--- indicator buffers double slowLineUpBuffer[],slowLineDownBuffer[],slowLineBuffer[],fastLineBuffer[],fastLineColorBuffer[],trendArrowColorBuffer[],trendArrowBuffer[],trendBuffer[]; //--- Indicator buffers //--- chart properties 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 //--- optimization flags static datetime lastRedrawTime = 0; //--- Last redraw time static double previousTrend = -1; //--- Previous trend string objectPrefix = "PTD_"; //--- Object prefix
ここでは、「#include <Canvas/Canvas.mqh>」を使用してCanvasライブラリをインクルードして、可視化を強化するためにインジケーターライン間のグラデーション塗りつぶしなどのカスタムグラフィック描画を可能にします。次に、領域を塗りつぶすためのビットマップCanvasを管理するために、CCanvasクラスのグローバルインスタンスとして「obj_Canvas」を宣言します。カスタマイズのための入力パラメータを定義します。fastPeriodは高速ピボット計算ウィンドウのデフォルト値として5に設定します。slowPeriodは低速ピボット計算ウィンドウのデフォルト値として10に設定します。upColorは上昇トレンド用にドジャーブルーに設定します。downColorは下降トレンド用にクリムゾンに設定します。fillOpacityは0から255の範囲でエリア塗りつぶしの不透明度として128(半透明)に設定します。arrowCodeはWingdingsのトレンド開始記号用に77に設定します。showExtensionsは現在のバーを超えてラインを延長するためにtrueに設定します。enableFillingはCanvasの塗りつぶしを切り替えるためにtrueに設定します(パフォーマンス向上のために無効化も可能です)。extendBarsはラインを延長するバー数として1に設定します。矢印のコードを変更することで、以下のようにMQL5で定義されているWingdingsフォントのいずれかを使用できます。

次に、インジケーターバッファとして8つのグローバル配列を割り当てます。slowLineUpBufferとslowLineDownBufferは上昇および下降の低速ラインを個別に格納するために使用します。slowLineBufferは内部の低速計算用です。fastLineBufferは高速ライン用です。fastLineColorBufferはその色を格納します。trendArrowColorBufferとtrendArrowBufferは矢印の位置と色を格納するために使用します。trendBufferはトレンド状態を保持します。チャートプロパティのためのグローバル変数を設定します。currentChartWidthとcurrentChartHeightは初期サイズとして0に設定します。currentChartScaleは0に設定します。firstVisibleBarIndexは左端のバーとして0に設定します。visibleBarsCountは0に設定します。minPriceとmaxPriceは価格範囲として0.0に設定します。最適化のために、lastRedrawTimeを静的変数として0に設定し、再描画の頻度を抑制します。previousTrendを静的変数として-1に設定し、変化検出に使用します。objectPrefixは拡張オブジェクトの命名用としてPTD_に設定します。コンパイルすると、以下の入力パラメータウィンドウが表示されます。

入力が完了したので、初期化イベントハンドラに進み、プログラムを初期化します。以下に、そのために使用したロジックを示します。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set chart properties currentChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width currentChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height currentChartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get chart scale firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get first visible bar visibleBarsCount = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars minPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get min price maxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get max price // Indicator buffers SetIndexBuffer(0,slowLineUpBuffer,INDICATOR_DATA); //--- Set slow up buffer SetIndexBuffer(1,slowLineDownBuffer,INDICATOR_DATA); //--- Set slow down buffer SetIndexBuffer(2,fastLineBuffer,INDICATOR_DATA); //--- Set fast buffer SetIndexBuffer(3,fastLineColorBuffer,INDICATOR_COLOR_INDEX); //--- Set fast color buffer SetIndexBuffer(4,trendArrowBuffer,INDICATOR_DATA); //--- Set arrow buffer SetIndexBuffer(5,trendArrowColorBuffer,INDICATOR_COLOR_INDEX); //--- Set arrow color buffer SetIndexBuffer(6,trendBuffer,INDICATOR_CALCULATIONS); //--- Set trend buffer SetIndexBuffer(7,slowLineBuffer,INDICATOR_CALCULATIONS); //--- Set slow buffer // Plot settings PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,slowPeriod); //--- Set slow draw begin PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,slowPeriod); //--- Set slow draw begin PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,fastPeriod); //--- Set fast draw begin PlotIndexSetInteger(3,PLOT_DRAW_BEGIN,fastPeriod); //--- Set fast draw begin PlotIndexSetInteger(4,PLOT_DRAW_BEGIN,slowPeriod); //--- Set arrow draw begin PlotIndexSetInteger(3,PLOT_ARROW,arrowCode); //--- Set arrow code // Line extensions PlotIndexSetInteger(0,PLOT_SHIFT,extendBars); //--- Set slow up shift PlotIndexSetInteger(1,PLOT_SHIFT,extendBars); //--- Set slow down shift PlotIndexSetInteger(2,PLOT_SHIFT,extendBars); //--- Set fast shift PlotIndexSetInteger(3,PLOT_SHIFT,0); //--- Set arrow shift // Set plot colors dynamically PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, upColor); //--- Set slow up color PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, downColor); //--- Set slow down color PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, upColor); //--- Set fast up color PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, downColor); //--- Set fast down color PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, upColor); //--- Set arrow up color PlotIndexSetInteger(4, PLOT_LINE_COLOR, 1, downColor); //--- Set arrow down color // Short name string shortName = "PTD(" + IntegerToString(fastPeriod) + "," + IntegerToString(slowPeriod) + ")"; //--- Set short name IndicatorSetString(INDICATOR_SHORTNAME, shortName); //--- Set indicator short name return(INIT_SUCCEEDED); //--- Return success }
インジケーターがチャートにアタッチされたとき、または再読み込みされたときに実行されるOnInitイベントハンドラでは、まず現在のチャートの寸法と表示パラメーターを取得して保存します。これらは後でCanvasレンダリングで必要になります。ChartGetIntegerを使用してCHART_WIDTH_IN_PIXELSを使用してピクセル単位の幅をcurrentChartWidthに、CHART_HEIGHT_IN_PIXELSを使用して高さをcurrentChartHeightに、CHART_SCALEを使用してスケールをcurrentChartScaleに、CHART_FIRST_VISIBLE_BARを使用して最初の表示バーをfirstVisibleBarIndexに、CHART_VISIBLE_BARSを使用して表示バー数をvisibleBarsCountに、ChartGetDoubleを使用して最小価格をCHART_PRICE_MINでminPriceに、CHART_PRICE_MAXを使用して最大価格をmaxPriceに保存します。これらの値により、現在のビューに基づいた適応的な描画が可能になります。
次に、8つのバッファをプロットにマッピングします。SetIndexBufferを適切な型で使用して、slowLineUpBufferをインデックス0にデータとして割り当て、slowLineDownBufferを1にデータとして割り当て、fastLineBufferを2にデータとして割り当て、fastLineColorBufferを3にカラーインデックスとして割り当て、trendArrowBufferを4にデータとして割り当て、trendArrowColorBufferを5にカラーインデックスとして割り当て、trendBufferを6に計算用として割り当て、slowLineBufferを7に計算用として割り当てます。プロットの描画開始はPlotIndexSetIntegerとPLOT_DRAW_BEGINで設定します。slowPeriodからの低速プロット、またはfastPeriodもしくはslowPeriodからの高速および矢印です。PLOT_ARROWを使用して、矢印プロットの記号をarrowCodeに設定します。拡張機能については、PLOT_SHIFTでシフトを適用します。低速の上下移動と高速の移動にはextendBars、矢印には0を指定します。PlotIndexSetIntegerとPLOT_LINE_COLORを使用してプロットの色を動的に設定します。インデックス0にはupColor、1にはdownColorを設定します。高速ラインのインデックス2には、0にupColor、1にdownColorを設定します。矢印のインデックス4も同様に設定します。「PTD(」+高速期間と低速期間をカンマで区切ったもの+")」という短い名前文字列を作成し、それをIndicatorSetStringとINDICATOR_SHORTNAMEで設定します。初期化が成功したことを確認するためにINIT_SUCCEEDEDを返します。コンパイルすると、次のようになります。

画像から、読み込み時にインジケーターを正確に設定したことがわかります。データウィンドウにバッファが表示されているので、次におこなうのは、それらのバッファとインジケーター計算にデータを入力し、私たちの戦略を使ってインジケーター値を取得することです。OnCalculateイベントハンドラ内で、以下のように処理をおこないます。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { // Always calculate buffers int startBar = prev_calculated - 1; //--- Set start bar if(startBar < 0) startBar = 0; //--- Adjust start bar for(int barIndex = startBar; barIndex < rates_total && !_StopFlag; barIndex++) { int fastStartBar = barIndex - fastPeriod + 1; //--- Calc fast start if(fastStartBar < 0) fastStartBar = 0; //--- Adjust fast start int slowStartBar = barIndex - slowPeriod + 1; //--- Calc slow start if(slowStartBar < 0) slowStartBar = 0; //--- Adjust slow start double slowHigh = high[ArrayMaximum(high, slowStartBar, slowPeriod)]; //--- Get slow high double slowLow = low[ArrayMinimum(low, slowStartBar, slowPeriod)]; //--- Get slow low double fastHigh = high[ArrayMaximum(high, fastStartBar, fastPeriod)]; //--- Get fast high double fastLow = low[ArrayMinimum(low, fastStartBar, fastPeriod)]; //--- Get fast low if(barIndex > 0) { slowLineBuffer[barIndex] = (close[barIndex] > slowLineBuffer[barIndex-1]) ? slowLow : slowHigh; //--- Set slow line fastLineBuffer[barIndex] = (close[barIndex] > fastLineBuffer[barIndex-1]) ? fastLow : fastHigh; //--- Set fast line trendBuffer[barIndex] = trendBuffer[barIndex-1]; //--- Set trend if(close[barIndex] < slowLineBuffer[barIndex] && close[barIndex] < fastLineBuffer[barIndex]) trendBuffer[barIndex] = 1; //--- Set up trend if(close[barIndex] > slowLineBuffer[barIndex] && close[barIndex] > fastLineBuffer[barIndex]) trendBuffer[barIndex] = 0; //--- Set down trend trendArrowBuffer[barIndex] = (trendBuffer[barIndex] != trendBuffer[barIndex-1]) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set arrow slowLineUpBuffer[barIndex] = (trendBuffer[barIndex] == 0) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow up slowLineDownBuffer[barIndex] = (trendBuffer[barIndex] == 1) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow down } else { trendArrowBuffer[barIndex] = slowLineUpBuffer[barIndex] = slowLineDownBuffer[barIndex] = EMPTY_VALUE; //--- Set empties trendBuffer[barIndex] = fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = 0; //--- Set zeros fastLineBuffer[barIndex] = slowLineBuffer[barIndex] = close[barIndex]; //--- Set first lines } fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = trendBuffer[barIndex]; //--- Set colors } return(rates_total); //--- Return total rates }
ここで、OnCalculateイベントハンドラは、新しいティックまたはバーごとに呼び出される主要な処理ハンドラであり、新しい価格データでインジケーターバッファを更新して、チャートが現在の市場状況を反映するようにします。計算の開始バーを「prev_calculated - 1」と決定し、負の場合は無効なインデックスを避けるために0に調整します。次にstartBarからrates_total-1まで、停止されていない間ループします。barIndexごとに、fast期間の開始位置を「barIndex - fastPeriod + 1」として計算し、0未満の場合は0に補正します。同様にslow期間の開始位置は「barIndex - slowPeriod + 1」として計算し、0未満の場合は0に補正します。slow期間のslowhighはhigh配列のslowStartBarからbarIndexまでの範囲に対してArrayMaximumを使用して最大値を取得し、slowlowはArrayMinimumを使用して最小値を取得します。fasthighはfast期間範囲の最大high、fastlowはfast期間範囲の最小lowとして計算します。
barIndex>0の場合、slowLineBuffer[barIndex]には、終値closeが直前のslowラインより上にある場合はslowlowを設定し上方向とし、それ以外の場合はslowhighを設定し下方向とします。fastLineBuffer[barIndex]には、終値closeが直前のfastラインより上にある場合はfastlowを設定し、それ以外の場合はfasthighを設定します。trendBuffer[barIndex]には前回のトレンドをコピーし、その後更新します。終値closeが現在のslowラインとfastラインの両方より下にある場合はトレンドを1として上昇に設定し、両方より上にある場合は0として下降に設定します。トレンドが以前から変化した場合は、trendArrowBuffer[barIndex]にはslowライン値で矢印を設定し、変化していない場合は空とします。slowLineUpBuffer[barIndex]にはトレンドが0のときslowラインを設定し、それ以外は空とします。slowLineDownBuffer[barIndex]にはトレンドが1のときslowラインを設定し、それ以外は空とします。最初のバーbarIndex==0では、矢印およびslowの上/下バッファをすべて空にし、trendおよびfastのカラーと矢印の色は0で初期化し、高速ラインと低速ラインは初期化としてclose[0]を設定します。カラーインデックス付けのために、トレンド値にfastLineColorBuffer[barIndex]とtrendArrowColorBuffer[barIndex]を割り当てます。処理されたすべてのバーを示すために、rates_totalを返します。コンパイルすると、次のようになります。

画像から、インジケーターが正しく計算され、チャート上に可視化されていること、およびバッファ配列にデータ値が格納されていることがわかります。あとは、インジケーター線の右側に価格を追加して、正確な線の価格を情報として把握できるようにするだけです。簡単です。モジュール性を高めるため、ロジックは関数内に格納します。
//+------------------------------------------------------------------+ //| Draw right price extension line/label | //+------------------------------------------------------------------+ bool drawRightPrice(string objectName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID) { bool objectExists = (ObjectFind(0, objectName) >= 0); //--- Check exists if(!objectExists) { if(!ObjectCreate(0, objectName, OBJ_ARROW_RIGHT_PRICE, 0, lineTime, linePrice)) { Print("Failed to create ", objectName); //--- Log failure return false; //--- Return failure } } else { ObjectSetInteger(0, objectName, OBJPROP_TIME, 0, lineTime); //--- Set time ObjectSetDouble(0, objectName, OBJPROP_PRICE, 0, linePrice); //--- Set price } long currentScale = ChartGetInteger(0, CHART_SCALE); //--- Get scale int lineWidth = 1; //--- Init width if(currentScale <= 1) lineWidth = 1; //--- Set width small else if(currentScale <= 3) lineWidth = 2; //--- Set width medium else lineWidth = 3; //--- Set width large 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_BACK, false); //--- Set foreground ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false); //--- Set not selectable ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false); //--- Set not selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } // we then call this function in the "OnCalculate" event handler // Draw line extensions if enabled if(showExtensions && rates_total > 0) { int latestBarIndex = rates_total - 1; //--- Get latest index double slowLineValue = slowLineBuffer[latestBarIndex]; //--- Get slow value double fastLineValue = fastLineBuffer[latestBarIndex]; //--- Get fast value double currentTrend = trendBuffer[latestBarIndex]; //--- Get trend color lineColor = (currentTrend == 0.0) ? upColor : downColor; //--- Set line color datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time long timeOffset = (long)extendBars * PeriodSeconds(_Period); //--- Calc offset datetime extensionTime = currentBarTime + (datetime)timeOffset; //--- Calc extension time drawRightPrice(objectPrefix + "SLOW", extensionTime, slowLineValue, lineColor, STYLE_SOLID); //--- Draw slow extension drawRightPrice(objectPrefix + "FAST", extensionTime, fastLineValue, lineColor, STYLE_DOT); //--- Draw fast extension }
適切な価格表示ロジックのために、drawRightPrice関数を定義し、入力設定に基づいてインジケーターラインを水平方向に右方向に延長し、将来のバーの視覚的な突出を提供する右価格矢印オブジェクトを作成または更新します。まず、ObjectFindを使用してオブジェクトが存在するかどうかを確認します。存在しない場合は、ObjectCreateを使用して指定されたlineTimeとlinePriceでOBJ_ARROW_RIGHT_PRICEを作成し、失敗した場合はログに記録してfalseを返します。存在する場合、OBJPROP_TIMEにはObjectSetInteger、OBJPROP_PRICEにはObjectSetDoubleを使用して、その時間と価格のアンカーを更新します。ChartGetIntegerとCHART_SCALEを使用して現在のチャートスケールを取得し、currentScaleに格納します。次に、スケールに基づいてlineWidthを設定します。1はスケールが1以下の場合、2はスケールが3以下の場合、3はそれ以上の場合に使用され、さまざまなズームレベルで視認性を確保します。
オブジェクトを設定します。色はOBJPROP_COLORによりlineColorを設定し、幅はlineWidthを設定し、スタイルはlineStyleを設定しデフォルトは実線とします。前面表示についてはOBJPROP_BACKをfalseに設定します。また選択可能状態はOBJPROP_SELECTABLEをfalseに設定し、選択状態はOBJPROP_SELECTEDをfalseに設定します。ChartRedraw関数を使ってチャートを再描画し、成功した場合はtrueを返します。showExtensionsがtrueでバーが存在する場合、OnCalculateイベントハンドラでこの関数を呼び出します。最新のインデックスを「rates_total - 1」として取得し、バッファから低速値と高速値を取得し、trendBufferからトレンドを取得し、トレンドが0.0の場合はupColor、それ以外の場合はdownColorとしてlineColorを選択し、シフト0のiTimeで現在のバー時間を取得し、「extendBars * PeriodSeconds (_Period)」としてオフセットを計算し、現在の時間プラスオフセットとしてエクステンション時間を取得し、次に「objectPrefix + "SLOW"」または「FAST」を使用して、低速の場合は実線スタイル、高速の場合はドットでdrawRightPriceを呼び出します。コンパイルすると、次のようになります。

適切な価格が算出されたことで、主要なインジケーターはすべて揃いました。あとは、インジケーターの境界線に合わせてCanvasをレンダリングするだけで完了です。そのためには、いくつかのヘルパー関数を定義します。
//+------------------------------------------------------------------+ //| Convert chart scale to bar width | //+------------------------------------------------------------------+ int BarWidth(int chartScale) { return (int)MathPow(2.0, chartScale); //--- Return bar width } //+------------------------------------------------------------------+ //| Convert bar shift to x pixel | //+------------------------------------------------------------------+ int ShiftToX(int barShift) { return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1); //--- Return x pixel } //+------------------------------------------------------------------+ //| Convert price to y pixel | //+------------------------------------------------------------------+ int PriceToY(double price) { if(maxPrice - minPrice == 0.0) return 0; //--- Return zero if no range return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1); //--- Return y pixel }
まず、現在のチャートスケールに基づいて各棒グラフのピクセル幅を計算する「BarWidth」関数を定義します。この関数は「MathPow (2.0, chartScale)」から整数を返します。これにより、Canvas座標での位置付けのための指数推定値(スケール0で1、1で2、2で4など)が得られます。次に、ShiftToX関数を実装して、バーのシフト(最も左にある表示バーを基準とした相対値)をチャート上のxピクセル位置に変換します。「(firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1」を整数にキャストして計算します。これにより、要素が右(最新)から左(古い)に配置され、整列のために1だけ調整されます。最後に、価格値をCanvas上のyピクセル座標にマッピングするPriceToY関数を作成します。価格範囲がない場合(「maxPrice - minPrice == 0.0」)は0を返し、それ以外の場合は「currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1」をMathRoundで整数に丸めます。これによりy軸が反転し(価格が高いほど上になります)、正確な描画のために1だけ調整されます。これからこれらの関数を使って、主要な処理をおこなうメイン関数を作成します。
//+-----------------------------------------------------------------------------------------+ //| Fill area between two lines using trend for color with gradient alpha from slow to fast | //+-----------------------------------------------------------------------------------------+ void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0) { int firstVisibleBar = firstVisibleBarIndex; //--- Get first visible int totalBarsToDraw = visibleBarsCount + extendShift; //--- Calc bars to draw int bufferSize = (int)ArraySize(slowLineValues); //--- Get buffer size if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return; //--- Return if invalid int previousX = -1; //--- Init previous X int previousY1 = -1; //--- Init previous Y1 int previousY2 = -1; //--- Init previous Y2 for(int offset = 0; offset < totalBarsToDraw; offset++) { int barPosition = firstVisibleBar - offset; //--- Calc bar position int x = ShiftToX(barPosition); //--- Calc x if(x >= currentChartWidth) break; //--- Break if beyond width int dataBarShift = firstVisibleBar - offset + extendShift; //--- Calc data shift int bufferBarIndex = bufferSize - 1 - dataBarShift; //--- Calc buffer index if(bufferBarIndex < 0 || bufferBarIndex >= bufferSize) { previousX = -1; //--- Reset previous X continue; //--- Continue } double value1 = slowLineValues[bufferBarIndex]; //--- Get value1 double value2 = fastLineValues[bufferBarIndex]; //--- Get value2 if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE) { previousX = -1; //--- Reset previous X continue; //--- Continue } int y1 = PriceToY(value1); //--- Calc y1 int y2 = PriceToY(value2); //--- Calc y2 double currentTrend = trendValues[bufferBarIndex]; //--- Get trend uint baseColorRGB = (currentTrend == 0.0) ? (ColorToARGB(fillUpColor, 255) & 0x00FFFFFF) : (ColorToARGB(fillDownColor, 255) & 0x00FFFFFF); //--- Set base RGB if(previousX != -1 && x > previousX) { double deltaX = x - previousX; //--- Calc delta X int endColumn = MathMin(x, currentChartWidth - 1); //--- Calc end column double maxT = (double)(endColumn - previousX) / deltaX; //--- Calc max T for(int column = previousX; column <= endColumn; column++) { double t = (column - previousX) / deltaX; //--- Calc t double interpolatedY1 = previousY1 + t * (y1 - previousY1); //--- Interpolate Y1 double interpolatedY2 = previousY2 + t * (y2 - previousY2); //--- Interpolate Y2 int upperY = (int)MathRound(MathMin(interpolatedY1, interpolatedY2)); //--- Calc upper Y int lowerY = (int)MathRound(MathMax(interpolatedY1, interpolatedY2)); //--- Calc lower Y if(upperY > lowerY) continue; //--- Continue if invalid double slowLineY = interpolatedY1; //--- Set slow Y double height = MathAbs(interpolatedY1 - interpolatedY2); //--- Calc height if(height == 0.0) continue; //--- Continue if no height // Fill per row with gradient from slow (opaque) to fast (transparent) for(int row = upperY; row <= lowerY; row++) { double distanceFromSlow = MathAbs(row - slowLineY); //--- Calc distance double gradientFraction = distanceFromSlow / height; //--- Calc fraction uchar alphaValue = (uchar)(fillAlpha * (1.0 - gradientFraction)); //--- Calc alpha if(alphaValue > fillAlpha) alphaValue = fillAlpha; //--- Cap alpha uint pixelColor = ((uint)alphaValue << 24) | baseColorRGB; //--- Set pixel color obj_Canvas.FillRectangle(column, row, column, row, pixelColor); //--- Fill pixel } } } previousX = x; //--- Update previous X previousY1 = y1; //--- Update previous Y1 previousY2 = y2; //--- Update previous Y2 } } //+------------------------------------------------------------------+ //| Redraw the canvas | //+------------------------------------------------------------------+ void Redraw(void) { if(currentChartWidth <= 0 || currentChartHeight <= 0) return; //--- Return if invalid size uint defaultColor = 0; //--- Default color obj_Canvas.Erase(defaultColor); //--- Erase canvas DrawFilling(slowLineBuffer, fastLineBuffer, trendBuffer, upColor, downColor, (uchar)fillOpacity, extendBars); //--- Draw filling obj_Canvas.Update(); //--- Update canvas }
DrawFilling関数は、Canvas上の低速ラインと高速ラインの間の領域をグラデーション塗りつぶしで描画するように定義されています。トレンドを使用して、低速ライン(完全なfillAlpha)から高速ライン(透明)に向かって色と不透明度を徐々に変化させ、滑らかな視覚的テーパーを作成し、有効になっている場合はextendShiftバーで拡張します。まず、最初に表示されるバーを取得し、表示されるバーの数にextendShiftを加えたtotalBarsToDrawを計算します。次に、slowLineValuesからバッファサイズを取得し、無効な場合や高速/トレンドバッファと一致しない場合は早期に処理を終了します。初期化として、補間追跡用のpreviousX、previousY1、previousY2をすべて-1に設定します。次に0から「totalBarsToDraw - 1」までのoffsetでループします。各ループごとに、バー位置を「firstVisibleBar - offset」として計算し、ShiftToXを用いてxピクセル位置を算出します。もしチャート幅を超える場合はループを終了します。データシフトは「visibleBar - offset」にextendShiftを加えて計算し、バッファインデックスは「size - 1 - dataShift」として求めます。この時、範囲外または値が空の場合はスキップし、previousXをリセットします。
次に、バッファから遅い値1と速い値2を取得し、PriceToYでy1/y2に変換し、trendValuesからトレンドを決定し、ColorToARGBマスクを使用してRGBにfillUpColorまたはfillDownColorからベースRGBを設定します。previousXが有効であり、かつ現在のXがpreviousXより大きい場合、補間処理をおこないます。まずdeltaXを計算します。次に、endColumnはXと「width - 1」の小さい方を取ります。 そしてmaxTは「(end - previous) / delta」によって算出します。前の列から最後の列まで、各列について、tを「(column - previous) / delta」として計算し、y1とy2を補間し、最小値/最大値を上限/下限Yに丸めます。上限 > 下限の場合はスキップします。slowLineYを補間されたy1に設定し、高さはy1からy2を引いた絶対値とし、ゼロの場合はスキップします。上から下へ各行について、slowからの距離を計算し、「distance / height」として分数を計算し、alphaを「fillAlpha * (1.0 - fraction)」としてucharにキャストし、fillAlphaで上限を設定し、ピクセルカラーを24ビットシフトしたアルファとして、ベースとORで結合し、列/行の1つのピクセルをobj_Canvas.FillRectangle"(1x1)で塗りつぶします。次の反復処理のために、前回のXを現在のXに、Y1をY1に、Y2をY2に更新します。
必要に応じてCanvasの描画を更新するためにRedraw関数を実装し、幅または高さが無効な場合(0以下)は早期に処理を終了します。デフォルトの色を0(透明)に設定します。obj_Canvas.Eraseを呼び出してCanvasを消去します。次にDrawFillingを呼び出し、slow、fast、trendの各バッファ、upおよびdownの色、fillOpacityをucharにキャストした値、さらにextendBarsを渡します。最後にobj_Canvas.Updateを実行してCanvasの表示を更新します。これは、計算イベントハンドラで以下に示すように、インジケーターの境界を塗りつぶしたいときに呼び出す関数です。
if(!enableFilling) return(rates_total); //--- Return if no filling // Canvas logic only if enabled bool isNewBar = (rates_total > prev_calculated); //--- Check new bar bool hasTrendChanged = false; //--- Init trend changed if(rates_total > 0 && trendBuffer[rates_total-1] != previousTrend) { hasTrendChanged = true; //--- Set changed previousTrend = trendBuffer[rates_total-1]; //--- Update previous trend } // Update chart properties (only if changed) bool hasChartChanged = false; //--- Init chart changed 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 visible int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get new min price double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get new max price if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) { 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) { currentChartScale = newChartScale; //--- Update scale firstVisibleBarIndex = newFirstVisibleBar; //--- Update first visible visibleBarsCount = newVisibleBars; //--- Update visible bars minPrice = newMinPrice; //--- Update min price maxPrice = newMaxPrice; //--- Update max price hasChartChanged = true; //--- Set changed } // Redraw only on: new bar, trend change, or chart resize/scroll. Debounce to 1x/sec max. datetime currentTime = TimeCurrent(); //--- Get current time if((isNewBar || hasTrendChanged || hasChartChanged) && (currentTime - lastRedrawTime >= 1)) { Redraw(); //--- Redraw canvas lastRedrawTime = currentTime; //--- Update last redraw }
ここでは、enableFillingがfalseの場合、rates_totalを早期に返してCanvasのロジックをスキップし、塗りつぶしが無効になっている場合のパフォーマンスを向上させます。次に、塗りつぶしが有効になっている場合にのみ、Canvas固有の操作を処理します。「rates_total > prev_calculated」で新しいバーをisNewBarにチェックし、バーが存在する場合は「trendBuffer[rates_total-1]」をpreviousTrendと比較することでトレンドの変化を検出し、hasTrendChangedをtrueに設定し、異なる場合はpreviousTrendを更新します。チャートの変更を監視します。hasChartChangedをfalseに初期化し、ChartGetInteger関数とChartGetDouble関数を使用して新しい幅/高さ/スケール/最初の表示バー/表示バー/最小価格/最大価格を取得します。幅または高さが異なる場合は、obj_Canvas.Resizeを使用してCanvasを新しい寸法にリサイズし、currentChartWidthとcurrentChartHeightを更新し、hasChartChangedをtrueに設定します。スケール、最初に表示される値、表示される値の数、最小価格または最大価格が変更された場合、それに応じてグローバル変数を更新し、hasChartChangedをtrueに設定します。
最後に、再描画を最適化します。TimeCurrentを使用して現在時刻を取得し、currentTimeに格納します。新しいバー、トレンドの変更、またはチャートの変更があり、lastRedrawTimeから少なくとも1秒経過している場合は、Redrawを呼び出してCanvasを更新し、lastRedrawTimeを現在時刻に更新します。これにより、デバウンス処理が最大でも1秒間に1回に制限され、不要な計算が削減されます。あとは、チャートイベントが検出されたときに変更を再レンダリングし、初期化解除時にそれらを削除するだけです。以下にその手順を示します。
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE || !enableFilling) return; int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) { obj_Canvas.Resize(newChartWidth, newChartHeight); //--- Resize canvas currentChartWidth = newChartWidth; //--- Update width currentChartHeight = newChartHeight; //--- Update height Redraw(); //--- Redraw canvas return; //--- Return } int newChartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get new scale int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get new min price double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get new max price if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount || newMinPrice != minPrice || newMaxPrice != maxPrice) { currentChartScale = newChartScale; //--- Update scale firstVisibleBarIndex = newFirstVisibleBar; //--- Update first visible visibleBarsCount = newVisibleBars; //--- Update visible bars minPrice = newMinPrice; //--- Update min price maxPrice = newMaxPrice; //--- Update max price Redraw(); //--- Redraw canvas } } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(enableFilling) obj_Canvas.Destroy(); //--- Destroy canvas if enabled ObjectsDeleteAll(0,objectPrefix,0,OBJ_ARROW_RIGHT_PRICE); //--- Delete right price arrows ChartRedraw(0); //--- Redraw chart }
ここでは、OnChartEventイベントハンドラを呼び出してチャート関連のイベントを処理します。具体的には、enableFillingがtrueに設定されて塗りつぶしが有効になっている場合にのみ変更に対応し、そうでない場合は早期に処理を終了します。まず、CHART_WIDTH_IN_PIXELSとCHART_HEIGHT_IN_PIXELSを使用してChartGetIntegerで新しいチャートの幅と高さを取得します。どちらかがcurrentChartWidthまたはcurrentChartHeightと異なる場合は、obj_Canvas.Resizeを使用してCanvasを新しい寸法にリサイズし、グローバル変数を更新し、Redrawを呼び出して塗りつぶしを更新して、戻ります。次に、CHART_SCALEで新しいスケールを取得し、CHART_FIRST_VISIBLE_BARで最初の表示バーを取得し、CHART_VISIBLE_BARSで表示バーを取得し、ChartGetDoubleとCHART_PRICE_MINで最小価格を取得し、CHART_PRICE_MAXで最大価格を取得します。保存されているグローバル変数からスケール、最初の表示、表示数、最小価格、最大価格のいずれかが変更された場合、currentChartScale、firstVisibleBarIndex、visibleBarsCount、minPrice、maxPriceを更新し、Redrawを呼び出してCanvasの塗りつぶしを新しいビューに合わせます。
OnDeinitイベントハンドラでは、インジケーターが削除されたとき、またはターミナルが終了するときに実行され、リソースを解放します。enableFillingがtrueの場合、obj_Canvas.Destroyを呼び出してCanvasを破棄します。次にObjectsDeleteAllを使用して、チャート0、ウィンドウ0、タイプOBJ_ARROW_RIGHT_PRICEを指定し、objectPrefixで始まるすべての右価格矢印オブジェクトを削除します。その後、ChartRedraw関数を呼び出してチャートを再描画します。コンパイルすると、次のようになります。

可視化から、インジケーターを計算し、許可されている場合はCanvasを塗りつぶすことで、目的を達成していることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

結論
高値/安値の範囲から高速ピボット線と低速ピボット線を計算し、色分けされた線と矢印でトレンドの方向を識別し、オプションで予測のために線を延長し、視覚的な奥行きのためにグラデーションcanvasで領域を塗りつぶし、新しいバーやチャートの変更時の再描画を最適化する、MQL5でピボットベースのトレンドインジケーターを作成しました。このインジケーターは、カスタマイズ可能な入力項目を備え、トレンド検出のための柔軟なツールを提供します。今後の記事では、機械学習の要素を取り入れたボラティリティチャネルやモメンタムオシレーターといった高度なインジケーターについて解説します。どうぞご期待ください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20610
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
ラリー・ウィリアムズの『市場の秘密』(第1回):MQL5でスイングストラクチャーインジケーターを構築する
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5入門(第31回):MQL5のAPIとWebRequest関数の習得(V)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索