English Deutsch
preview
MQL5でカスタムインジケーターを作成する(第1回):Canvasグラデーションを使用したピボットベースのトレンドインジケーターの構築

MQL5でカスタムインジケーターを作成する(第1回):Canvasグラデーションを使用したピボットベースのトレンドインジケーターの構築

MetaTrader 5トレーディング |
14 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事では、高速/低速ピボットラインを計算し、方向矢印でトレンドを検出し、チャート上でピボットラインを前方に延長し、読みやすさを向上させるために、強気または弱気の領域を強調表示するオプションのCanvasグラデーションを提供する、MetaQuotes Language 5 (MQL5)でピボットベースのトレンドインジケーターを開発します。この記事で取り上げるトピックは以下の通りです。

  1. ピボットベースのトレンドインジケーターフレームワークの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的には、柔軟な視覚設定を備えた、ピボットトレンド検出用の完全に機能する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フォントのいずれかを使用できます。

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に計算用として割り当てます。プロットの描画開始はPlotIndexSetIntegerPLOT_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(」+高速期間と低速期間をカンマで区切ったもの+")」という短い名前文字列を作成し、それをIndicatorSetStringINDICATOR_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を使用して、その時間と価格のアンカーを更新します。ChartGetIntegerCHART_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_PIXELSCHART_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関数を呼び出してチャートを再描画します。コンパイルすると、次のようになります。

PIVOT TREND DETECTORインジケーターテストGIF

可視化から、インジケーターを計算し、許可されている場合はCanvasを塗りつぶすことで、目的を達成していることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

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

バックテストGIF


結論

高値/安値の範囲から高速ピボット線と低速ピボット線を計算し、色分けされた線と矢印でトレンドの方向を識別し、オプションで予測のために線を延長し、視覚的な奥行きのためにグラデーションcanvasで領域を塗りつぶし、新しいバーやチャートの変更時の再描画を最適化する、MQL5でピボットベースのトレンドインジケーターを作成しました。このインジケーターは、カスタマイズ可能な入力項目を備え、トレンド検出のための柔軟なツールを提供します。今後の記事では、機械学習の要素を取り入れたボラティリティチャネルやモメンタムオシレーターといった高度なインジケーターについて解説します。どうぞご期待ください。

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

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
ラリー・ウィリアムズの『市場の秘密』(第1回):MQL5でスイングストラクチャーインジケーターを構築する ラリー・ウィリアムズの『市場の秘密』(第1回):MQL5でスイングストラクチャーインジケーターを構築する
MQL5でラリー・ウィリアムズ式の市場構造インジケーターを構築するための実践的なガイドです。バッファの設定、スイングポイントの検出、チャートの設定、そしてトレーダーがテクニカル市場分析でこのインジケーターをどのように活用できるかについて解説します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5入門(第31回):MQL5のAPIとWebRequest関数の習得(V) MQL5入門(第31回):MQL5のAPIとWebRequest関数の習得(V)
WebRequestと外部API呼び出しの使い方を学び、最新のローソク足データを取得し、各値を使用可能な型へ変換し、テーブル形式で整理して保存する方法を解説します。このステップは、取得したデータをローソク足形式で可視化するインジケーターを構築するための基礎となります。