【MT5/MQL5】【独自インジケータ】高値安値描画(MTF対応、リペイント無し)

この記事は約30分で読めます。

今回作成するプログラムの概要

今回は、高値と安値を検出して描画するインジケータを紹介したいと思います。このインジケータは、私が作成した独自インジケータの基礎となっている部分を抜粋したものです。シンプルなインジケータですが、マルチタイムフレーム対応でリペイントもしないので、使い勝手は良いと思います。

当記事の最後にコンパイル済インジケータをダウンロードできるようにしていますので、すぐ使いたいという方は、当記事の最後からダウンロードしてください。

高値・安値検出の仕組み

まずは、このインジケータで、どのように高値と安値を検出しているのかを、図を交えて説明したいと思います。

高値・安値が検出されたケース

当インジケータでは、「高値確認期間Bar数」と「高値判定前後Bar数」を利用して、高値・安値をつけたかを判断しています。高値検出のケースで説明すると、「高値確認期間Bar数」の範囲内での高値を探し、その「期間内高値」の前後数Bar(「高値判定前後Bar数」で設定)が、「高値確認期間Bar数」の範囲内に収まっている場合、「高値をつけた」と判断します。

高値・安値が検出されなかったケース

次に、高値・安値が検出されなかったケースを説明します。高値のケースで説明すると、「高値確認期間Bar数」の範囲内での高値を探し、その「期間内高値」の前後数Bar(「高値判定前後Bar数」で設定)が、「高値確認期間Bar数」の範囲内に収まっていない場合、高値更新中なので「高値をつけた」とは判断しません。

インジケータのMQL5コード

コード

インジケータのコードを紹介します。今回は「高値安値描画インジケータ(IndicatorSimpleHighLow.mq5)」の1ファイルのみになります。内部で利用している関数も同ファイルの中で定義しているため、行数は多くなっています。

高値安値描画インジケータ(IndicatorSimpleHighLow.mq5)

//+------------------------------------------------------------------+
//|                                       IndicatorSimpleHighLow.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| インジケータ関連定義
//+------------------------------------------------------------------+
#property indicator_chart_window

#property indicator_buffers             2
#property indicator_plots               2

#define   SIMPLE_HIGH_LOW_IDX_HIGH      0
#define   SIMPLE_HIGH_LOW_IDX_LOW       1

#property indicator_label1              "高値"
#property indicator_type1               DRAW_LINE
#property indicator_style1              STYLE_SOLID
#property indicator_color1              clrBlue
#property indicator_width1              1
double    bufHigh[];

#property indicator_label2              "安値"
#property indicator_type2               DRAW_LINE
#property indicator_style2              STYLE_SOLID
#property indicator_color2              clrRed
#property indicator_width2              1
double    bufLow[];

enum ENUM_HIGH_LOW_MODE
  {
   HLMODE_HIGH_LOW                      = 0, // 高値-安値
   HLMODE_OPEN_CLOSE                    = 1  // 始値-終値
  };

datetime    gdtTgtBeforeBarTimeWithOrgBar; // 画面表示中の時間足チャートにおける、計算用時間足チャートの前Bar時刻
int         giTgtBeforeBarWithOrgBar;      // 画面表示中の時間足チャートにおける、計算用時間足チャートの前Bar位置

//+------------------------------------------------------------------+
//| インジケータ入力パラメータ
//+------------------------------------------------------------------+

input  group              "【 基本設定 】"
input  ENUM_TIMEFRAMES    inp_calc_timeframe             =PERIOD_CURRENT;       // 指標値計算対象の時間足

input  group              "【 高値/安値 】"
input  int                inp_period_bars                =10;                   // 高値/安値確認期間Bar数
input  int                inp_judge_bars                 =3;                    // 高値/安値判定前後Bar数
input  ENUM_HIGH_LOW_MODE inp_mode                       =HLMODE_HIGH_LOW;      // 高値/安値判定モード


//+------------------------------------------------------------------+
//| 【初期化関数】
//|  ・チャートの初期表示時、または時間足変更等のチャート初期化が必要な
//|    タイミングで呼び出される。
//+------------------------------------------------------------------+
int OnInit()
  {

//+------------------------------------------------------------------+
//| 入力パラメータチェック
//+------------------------------------------------------------------+
   if(inp_period_bars < 5)
     {
      return(INIT_PARAMETERS_INCORRECT);
     }

//+------------------------------------------------------------------+
//| インジケータバッファ設定
//+------------------------------------------------------------------+
   ArraySetAsSeries(bufHigh,                           true);
   ArrayInitialize(bufHigh,                            0);
   SetIndexBuffer(SIMPLE_HIGH_LOW_IDX_HIGH,            bufHigh,                 INDICATOR_DATA);
   PlotIndexSetDouble(SIMPLE_HIGH_LOW_IDX_HIGH,        PLOT_EMPTY_VALUE,        0.0);

   ArraySetAsSeries(bufLow,                            true);
   ArrayInitialize(bufLow,                             0);
   SetIndexBuffer(SIMPLE_HIGH_LOW_IDX_LOW,             bufLow,                  INDICATOR_DATA);
   PlotIndexSetDouble(SIMPLE_HIGH_LOW_IDX_LOW,         PLOT_EMPTY_VALUE,        0.0);

//+------------------------------------------------------------------+
//| 変数初期化
//+------------------------------------------------------------------+
   gdtTgtBeforeBarTimeWithOrgBar      = NULL;
   giTgtBeforeBarWithOrgBar           = 0;

   return(INIT_SUCCEEDED);

  }


//+------------------------------------------------------------------+
//| 【計算イベント関数】
//|  ・ローソク足に変化が発生する毎に呼び出される。
//+------------------------------------------------------------------+
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[])
  {
//+------------------------------------------------------------------+
//| 1. 新規チャートのインジケータ値がまだ計算されていない場合
//|   → 過去チャートのインジケータ値を計算する。
//+------------------------------------------------------------------+
   if(prev_calculated == 0)
     {
      for(int i=rates_total-inp_period_bars-1; i>=0; i--)
        {
         setBufferValue(i,false);
        }
     }

//+------------------------------------------------------------------+
//| 2. 過去チャートのインジケータ値は計算済で、
//|    前回計算済Bar数 < チャートの最大Bar数 の場合 (Liveで新規Barが発生した)
//|    → 新規Barのインジケータ値を計算する。
//+------------------------------------------------------------------+
   else
     {
      if(prev_calculated < rates_total)
        {
         setBufferValue(0,true);
        }
     }

//+------------------------------------------------------------------+
//| 3. 同Bar内でのティック変更によって呼び出された場合
//|    → インジケータ値は計算しない
//+------------------------------------------------------------------+

// NOP

//+------------------------------------------------------------------+
//| 今回の処理でインジケータ値を計算したBar数を返却する。
//|   → このreturn値は、次回OnCalculate関数が呼び出された際、
//|     引数のprev_calculatedに設定される。
//+------------------------------------------------------------------+

   return(rates_total);

  }


//+------------------------------------------------------------------+
//| 【インデックスバッファ設定関数】
//|  ・指定されたBar位置のインジケータ値を計算する。
//+------------------------------------------------------------------+
void setBufferValue(int iOrgCurrentBar, bool bIsNewBar)
  {

//+------------------------------------------------------------------+
//| 変数定義
//+------------------------------------------------------------------+
// 時間足調整用
   datetime dtOrgCurrentTime;
   int      iTgtCurrentBar        = 0;      // 計算用時間足の Bar+0 位置
   int      iTgtBeforeBar         = 0;      // 計算用時間足の Bar+1 位置
   bool     bIsNewHigh            = false;  // 新高値検出フラグ
   int      iTgtHighestBar        = 0;      // 高値Bar位置
   double   dHighestPrice         = 0;      // 高値プライス
   bool     bIsNewLow             = false;  // 新安値検出フラグ
   int      iTgtLowestBar         = 0;      // 安値Bar位置
   double   dLowestPrice          = 0;      // 安値プライス

//+------------------------------------------------------------------+
//| 現在Bar位置のインジケータ値を初期化
//+------------------------------------------------------------------+
   bufHigh[iOrgCurrentBar]        = 0;
   bufLow[iOrgCurrentBar]         = 0;

//+------------------------------------------------------------------+
//| 計算用時間足のBar位置(T+0,T+1)を、現在時刻から算出
//+------------------------------------------------------------------+
   dtOrgCurrentTime   = iTime(Symbol(),     PERIOD_CURRENT,       iOrgCurrentBar);
   iTgtBeforeBar      = iBarShift(Symbol(), inp_calc_timeframe,   gdtTgtBeforeBarTimeWithOrgBar, false);
   iTgtCurrentBar     = iBarShift(Symbol(), inp_calc_timeframe,   dtOrgCurrentTime, false);

//+------------------------------------------------------------------+
//| 計算用時間足のBar位置が変わってない場合(計算用時間足ではバー未確定)
//| 前Barのインジケータ値を引き継いで終了
//+------------------------------------------------------------------+
   if(iTgtBeforeBar == iTgtCurrentBar)
     {
      bufHigh[iOrgCurrentBar]     = bufHigh[iOrgCurrentBar + 1];
      bufLow[iOrgCurrentBar]      = bufLow[iOrgCurrentBar  + 1];
      return;
     }

//+------------------------------------------------------------------+
//| 計算用時間足のBar位置が変わった場合、インジケータ値を算出する。
//+------------------------------------------------------------------+
   gdtTgtBeforeBarTimeWithOrgBar    = dtOrgCurrentTime;
   giTgtBeforeBarWithOrgBar         = iOrgCurrentBar + 1;

//+------------------------------------------------------------------+
//| 新高値検出判定
//|   「高値/安値確認期間Bar数」期間内の高値Bar位置が
//|   「高値確認期間Bar数」の範囲に収まっているかを確認する。
//+------------------------------------------------------------------+

// 「高値/安値確認期間Bar数」期間内の高値Bar位置、高値プライスを取得
   getHighestPosPrice(
      Symbol(),              // [IN ]:通貨ペア
      inp_calc_timeframe,    // [IN ]:指標値計算対象の時間足
      inp_mode,              // [IN ]:高安判定モード
      iTgtCurrentBar + 1,    // [IN ]:高値検索開始Bar位置(計算用時間足のBar位置[T+1])
      inp_period_bars,       // [IN ]:高値/安値確認期間Bar数
      iTgtHighestBar,        // [OUT]:高値Bar位置
      dHighestPrice);        // [OUT]:高値プライス

// 新高値検出
   if(
      iTgtCurrentBar + inp_judge_bars + 1 <= iTgtHighestBar
      && iTgtHighestBar <= iTgtCurrentBar + inp_period_bars - inp_judge_bars
   )
     {
      bIsNewHigh=true;
     }

//+------------------------------------------------------------------+
//| 新安値検出判定
//|   「高値/安値確認期間Bar数」期間内の高値Bar位置が
//|   「高値確認期間Bar数」の範囲に収まっているかを確認する。
//+------------------------------------------------------------------+

// 期間内の安値Bar位置、安値プライスを取得
   getLowestPosPrice(
      Symbol(),              // [IN ]:通貨ペア
      inp_calc_timeframe,    // [IN ]:指標値計算対象の時間足
      inp_mode,              // [IN ]:高安判定モード
      iTgtCurrentBar + 1,    // [IN ]:開始Bar
      inp_period_bars,       // [IN ]:期間Bars
      iTgtLowestBar,         // [OUT]:安値Bar位置
      dLowestPrice);         // [OUT]:安値プライス

// 新安値検出
   if(
      iTgtCurrentBar + inp_judge_bars + 1 <= iTgtLowestBar
      && iTgtLowestBar <= iTgtCurrentBar + inp_period_bars - inp_judge_bars
   )
     {
      bIsNewLow = true;
     }

//+------------------------------------------------------------------+
//| 検出した新高値・新安値を更新すべきかを判断する。
//| 安値<高値の関係性を維持できる時のみ、新高値、新安値を更新する。
//+------------------------------------------------------------------+

// 初期値として前回と同じ値を設定する
   bufHigh[iOrgCurrentBar] = bufHigh[giTgtBeforeBarWithOrgBar];
   bufLow[iOrgCurrentBar]  = bufLow[giTgtBeforeBarWithOrgBar];

// 新高値を検出した場合
   if(bIsNewHigh == true)
     {
      // 新安値も同時に検出した場合
      if(bIsNewLow == true)
        {
         // 新安値 < 新高値の場合、両方更新する
         if(dLowestPrice < dHighestPrice)
           {
            if(bufHigh[iOrgCurrentBar] != dHighestPrice)
              {
               bufHigh[iOrgCurrentBar] = dHighestPrice;
              }
            if(bufLow[iOrgCurrentBar] != dLowestPrice)
              {
               bufLow[iOrgCurrentBar] = dLowestPrice;
              }
           }
        }
      // 新高値のみ検出した場合
      else
        {
         // 前回安値 < 新高値の場合、高値を更新する
         if(bufLow[giTgtBeforeBarWithOrgBar] < dHighestPrice)
           {
            if(bufHigh[iOrgCurrentBar] != dHighestPrice)
              {
               bufHigh[iOrgCurrentBar] = dHighestPrice;
              }
           }
        }
     }
// 新安値を検出した場合
   else
     {
      if(bIsNewLow == true)
        {
         // 新安値 < 前回高値の場合、新安値を更新する
         if(dLowestPrice < bufHigh[giTgtBeforeBarWithOrgBar])
           {
            if(bufLow[iOrgCurrentBar] != dLowestPrice)
              {
               bufLow[iOrgCurrentBar] = dLowestPrice;
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| 【関数】高値Bar位置と高値プライスを取得する
//|     高値安値判定モードが "HLMODE_OPEN_CLOSE" の場合、
//|     ローソク足実体(始値~終値)基準のでの高値を返却する。
//+------------------------------------------------------------------+
void getHighestPosPrice(
   string              sArgSymbol,              // [IN ] シンボル
   ENUM_TIMEFRAMES     enArgTimeframe,          // [IN ] 時間軸
   ENUM_HIGH_LOW_MODE  iArgMode,                // [IN ] 高値安値判定モード
   int                 iArgBar,                 // [IN ] 基準Bar位置
   int                 iArgRange,               // [IN ] 期間Bar数
   int                 &iOutHighestPos,         // [OUT] 高値Bar位置
   double              &dOutHighestPrice        // [OUT] 高値価格
)
  {
   int    iPos;
   int    iPosOpen;
   int    iPosClose;
   double dPriceOpen;
   double dPriceClose;
   double dTmpHigh;

   iOutHighestPos=-1;
   dOutHighestPrice=0;

   if(iArgMode==HLMODE_HIGH_LOW)
     {
      iPos               = iHighest(sArgSymbol,enArgTimeframe,MODE_HIGH,iArgRange,iArgBar);
      iOutHighestPos     = iPos;
      dTmpHigh           = iHigh(sArgSymbol,enArgTimeframe,iPos);
      dOutHighestPrice   = dTmpHigh;
     }
   else
     {
      iPosOpen           = iHighest(sArgSymbol,enArgTimeframe,MODE_OPEN,iArgRange,iArgBar);
      iPosClose          = iHighest(sArgSymbol,enArgTimeframe,MODE_CLOSE,iArgRange,iArgBar);
      dPriceOpen         = iOpen(sArgSymbol,enArgTimeframe,iPosOpen);
      dPriceClose        = iClose(sArgSymbol,enArgTimeframe,iPosClose);
      if(dPriceOpen > dPriceClose)
        {
         iPos            = iPosOpen;
         iOutHighestPos  = iPos;
         dTmpHigh        = dPriceOpen;
        }
      else
        {
         iPos            = iPosClose;
         iOutHighestPos  = iPos;
         dTmpHigh        = dPriceClose;
        }
      dOutHighestPrice   = dTmpHigh;
     }
   return;

  }

//+------------------------------------------------------------------+
//| 【関数】安値Bar位置と安値プライスを取得する
//|     高値安値判定モードが "HLMODE_OPEN_CLOSE" の場合、
//|     ローソク足実体(始値~終値)基準のでの安値を返却する。
//+------------------------------------------------------------------+
void getLowestPosPrice(
   string              sArgSymbol,              // [IN ] シンボル
   ENUM_TIMEFRAMES     enArgTimeframe,          // [IN ] 時間軸
   ENUM_HIGH_LOW_MODE  iArgMode,                // [IN ] 高値安値判定モード
   int                 iArgBar,                 // [IN ] 基準Bar位置
   int                 iArgRange,               // [IN ] 期間Bar数
   int                 &iOutLowestPos,          // [OUT] 高値Bar位置
   double              &dOutLowestPrice         // [OUT] 高値価格
)
  {
   int    iPos;
   int    iPosOpen;
   int    iPosClose;
   double dPriceOpen;
   double dPriceClose;
   double dTmpLow;

   iOutLowestPos=-1;
   dOutLowestPrice=0;

   if(iArgMode==HLMODE_HIGH_LOW)
     {
      iPos               = iLowest(sArgSymbol,enArgTimeframe,MODE_LOW,iArgRange,iArgBar);
      iOutLowestPos      = iPos;
      dTmpLow            = iLow(sArgSymbol,enArgTimeframe,iPos);
      dOutLowestPrice    = dTmpLow;
     }
   else
     {
      iPosOpen           = iLowest(sArgSymbol,enArgTimeframe,MODE_OPEN,iArgRange,iArgBar);
      iPosClose          = iLowest(sArgSymbol,enArgTimeframe,MODE_CLOSE,iArgRange,iArgBar);
      dPriceOpen         = iOpen(sArgSymbol,enArgTimeframe,iPosOpen);
      dPriceClose        = iClose(sArgSymbol,enArgTimeframe,iPosClose);
      if(dPriceOpen > dPriceClose)
        {
         iPos            = iPosClose;
         iOutLowestPos   = iPos;
         dTmpLow         = dPriceClose;
        }
      else
        {
         iPos            = iPosOpen;
         iOutLowestPos   = iPos;
         dTmpLow         = dPriceOpen;
        }
      dOutLowestPrice    = dTmpLow;
     }
   return;
  }
//+------------------------------------------------------------------+
Expand

コード解説

高値安値描画インジケータ(IndicatorSimpleHighLow.mq5)

41,42,89,90行目

マルチタイムフレーム対応用の変数と初期化処理です。画面に表示しているチャートの時間足を基準にして、計算すべき時間足チャートのBar時刻とBar位置を保持します。

116~150行目

新規ローソク足発生時のみ計算する処理になっていますが、解説はこちらの記事を参照してください。

182~204行目

ソースのコメントにも記載してありますが、計算用時間足のBar位置(T+0,T+1)を、表示中時間足の現在時刻から算出します。計算用時間足のBar位置が変わった場合のみ、インジケータ値を計算します。
ちょっと分かりづらいと思うので具体例で説明すると、

  • 表示用時間足:5分足
  • 計算用時間足:1時間足

の場合、49分から50分になったタイミングでは5分足は1Bar分進みますが、1時間足はまだ進まないため、新たなインジケータ値の計算は行わずに、前Barの値を引き継ぎます。

59分から00分になったタイミングでは、1時間足も1Bar分進むため、インジケータ値を計算します。

206~255行目

前述した、高値・安値を検出される処理に従って新高値・新安値を検出します。

256~312行目

検出された新高値・新安値を更新すべきかを判断します。安値<高値の関係性を維持できる時のみ、新高値、新安値を更新します。また、場合によっては以下のようなイレギュラーケースも発生するため、その対応もしています。

  • 高値と安値が同じBarで検知される
  • 高値が検知されないまま、高値より高い「安値」が検知されてしまう

314~423行目

インジケータ処理から呼び出している、期間内の高値・安値を取得する内部関数です。引数の「高値安値判定モード」にHLMODE_HIGH_LOW(始値-終値)を指定すると、実体足ベースでの高値・安値を取得します。

実行結果

高値安値描画インジケータの実行結果は以下のようになります。高値・安値としてピックアップしてほしいポイントが概ね描画されていると思います。

次は15分足チャートに「指標値計算対象の時間足」に1時間足を設定して描画した結果です。
細いラインが15分足基準で検出した高値・安値、太いラインが1時間足基準で検出した高値・安値です。ちゃんとマルチタイムフレームで動作しています。

インジケータのインプットパラメータは下図のようになります。

まとめ

今回は、高値安値を描画するインジケータを紹介しました。

高値と安値を描画するインジケータは世の中に色々あると思います。有名なものでいうとZigZagインジケータがありますが、高値・安値として拾ってほしいポイントを微妙にスルーしてしまったり、当然のようにリペイントもします。他にも色々と探してみましたが、満足できるものが見つけられなかった(というよりも途中から探すのが面倒になった)ので、結局自分で作りました(笑)。

実際作ってみると、イレギュラーなケース(高値と安値が同じBarで検知される、高値が検知されないまま、高値より高い「安値」が検知されてしまう、等)があり苦労はしましたが、なんとか時間をかけて解決しました。

シンプルなインジケータなので、あくまでトレードの補助的な位置付けにはなりますが、オブジェクトで水平線を引く手間はだいぶ省けるので、使って頂ければと思います。

ダウンロード

今回紹介したインジケータはこちらからダウンロードできます。

コメント

タイトルとURLをコピーしました