作りたいもの
ZigZag は便利だけど、どうパラメータを調整しても自分の感覚に近い波形を描いてくれないんですよね。
そこで、自分用の ZigZag的なインジケータの作成を試みます。
なお、参考までに、MT4/MT5 における ZigZag の仕様を、Technical-Bookさんのサイトで確認しました。
参考サイト
空のコードの用意
ウィザードを使用して、インジケータ用の空のコードを用意する手順を示します。
※ 画像をクリックすると、大きく表示されます。

ツールバーの 新規作成 をクリック。

カスタムインジケータ を選択し、次へ をクリック。

ファイル名を入力する。
初期状態で Indicators が入力されているので、その後ろにファイル名を入力する。
左の例では、Indicators の下に Hailu フォルダを作成し、その中に ZigZaaag というファイルが作成される。
著作権、リンクはお好みで。
パラメータは、今回は空にしておく。
次へ をクリック。

イベントハンドラを選択する。
全部チェックをつけてもいい。
今回は OnCalculation だけチェックを付けておく。
次へ をクリック。

今回はそのまま 完了 をクリック。
オシレータ系の指標のように、チャートと別の領域に表示したい場合は、サブウィンドウに表示 を選択する。
すると、いくつか空のイベントハンドラが書かれたコードが表示されるので、このコードを変更していきます。
線の描画方法
チャート上への線の描画の仕方を示します。
例として、次のようなインジケータを作ってみます。
- 全てのローソクの始値を線でつなぐ
- ローソク3本置きに、終値を線でつなぐ
コード全体は以下のとおり。
//+------------------------------------------------------------------+
//| ZigZaaag.mq5 |
//| Copyright 2024, Hailu |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Hailu"
#property link "https://www.mql5.com"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrLime
#property indicator_width1 3
#property indicator_type2 DRAW_SECTION
#property indicator_color2 clrWhite
#property indicator_width2 2
#property indicator_style2 STYLE_DASH
double bufOpen[];
double bufClose[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
SetIndexBuffer(0, bufOpen, INDICATOR_DATA);
SetIndexBuffer(1, bufClose, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| 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[])
{
for (int i = 0; i < rates_total; i++)
{
bufOpen[i] = open[i];
if ((i % 3) == 0)
bufClose[i] = close[i];
else
bufClose[i] = 0;
}
return rates_total;
}
//+------------------------------------------------------------------+
内容を簡単に説明します。
線のスタイルと頂点の配列
#property indicator_buffers 2
#property indicator_plots 2
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrLime
#property indicator_width1 3
#property indicator_type2 DRAW_SECTION
#property indicator_color2 clrWhite
#property indicator_width2 2
#property indicator_style2 STYLE_DASH
最初に、プロパティを定義します。
これらの中には、チャート上で設定を変更できるものもあります。
プロパティ | 内容 |
indicator_buffers | 描画する線の頂点の配列と、作業用の配列の数を指定します。 今回は作業用の配列は使用しないので、2を指定します。 |
indicator_plots | 描画する線の数です。 |
indicator_type〇 | インジケータの描画スタイルです。 DRAW_LINE は、すべてのロウソクの時刻に頂点を設けるときに使用します。 DRAW_SECTION は、ZigZagのように、飛び飛びに頂点を設けるときに使用します。 〇の部分には1起算の連番を記述します。 今回は線を2本引くので、1 と 2 を指定します。 |
indicator_color〇 | 線の色の初期値です。 チャート上で変更可能です。 |
indicator_width〇 | 線の太さです。 チャート上で変更可能です。 |
indicator_style〇 | 線種です。 STYLE_SOLID(実線)、STYLE_DASH(破線)、STYLE_DOT(点線) などがあります。 ただし、なぜか太さを2以上にすると、点線を設定しても実線になってしまいます。 指定しなければ実線になります。 チャート上で変更可能です。 |
次に、線の頂点を格納する動的配列を宣言します。
作業用の配列を用意する場合は、ここに一緒に書いておきます。
double bufOpen[];
double bufClose[];
イベントハンドラ
使用するイベントハンドラは、以下の2つです。
イベントハンドラ | 実行されるタイミング |
OnInit | カスタムインジケータが起動し、グローバル変数が初期化された後。 チャートの時間足を変更したときにも呼ばれる。 |
OnCalculate | 値動きがあったとき。 |
OnInit()
int OnInit()
{
SetIndexBuffer(0, bufOpen, INDICATOR_DATA);
SetIndexBuffer(1, bufClose, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);
return INIT_SUCCEEDED;
}
SetIndexBuffer関数は、上で定義した動的配列にインデックスを付けます。
この関数に渡した動的配列は、自動で適切に領域を確保してくれます。
SetIndexBuffer関数の定義は以下のとおり。
bool SetIndexBuffer(
int index, // バッファインデックス
double buffer[], // 配列
ENUM_INDEXBUFFER_TYPE data_type // 格納されるもの
);
data_type は、指標値の配列には INDICATOR_DATA、計算用の配列には INDICATOR_CALCULATIONS を指定します。
OnCalculate()
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[])
{
for (int i = 0; i < rates_total; i++)
{
bufOpen[i] = open[i];
if ((i % 3) == 0)
bufClose[i] = close[i];
else
bufClose[i] = 0;
}
return rates_total;
}
ここで、描画したい線の頂点の配列 (この例では bufOpen と bufClose) に価格を設定すると、チャート上に線を引いてくれます。
思ったより簡単です。
コンパイルすると、ナビゲータウィンドウの指標の下に、作成したインジケータが表示されます。

これをチャートに ドラッグ&ドロップ すると、次のように表示されます。

期待通り、始値が実線で結ばれていて、終値が3本置きに破線で結ばれています。
線の描画方法についてはここまでにして、いよいよ、自分用のZigZag的なインジケータを作っていきます。
ZigZag的インジケータ
まずは、単純に上昇から下降に切り替わるところの高値と、下降から上昇に切り替わるところの安値を結んでみます。
白色の線が作成中のインジケータ。
緑色の線は、比較のために表示した ZigZag (Depth=12, Backstep=3)。

うん。だいぶジグザグしてますね。
ここから余計な頂点を省いていくのですが、
ルールどうしよう ..................
自分の感覚の言語化か ..................
ZigZag って優秀だね!
ちょっと体動かして、頭切り替えてきました。
え~~~っと、ある程度高い山・深い谷は残して、低い山・浅い谷を取り除きたい。
この「ある程度」と、取り除く範囲 (ZigZag の Depth, Backstep のようなもの) を定義する。
例えば、下図の①の高値が確定したときに、②の点を取り除きたい。
③も取り除きたいかも。
このとき、どこまで遡るかをロウソクの本数ではなく、価格の推移から判断したい。

高値が確定したとき、
取り除く範囲:
確定した高値 (図の①) から 安値が切り上がる手前 (図の④) まで
ある程度:
切り上がる手前の安値 (図の④) から 確定した高値 (図の①) までの値幅の一定割合(プロパティにしておく)より小さい調整波で構成された谷を取り除く。
安値が確定したときは、これの逆を実施する。
もしかして、TradingView の ZigZag の考え方に近い?
ちゃんと理解してないけど、なんか近そうですね。ちょっと違うみたいけど。
少なくとも、TradingView のように、初期設定で線が引かれないという事態にはならないはず。
とりあえず実装してみます。
追加前

追加後

ましにはなったけど、ちょっと違うんだよなぁ...(´・ω・`)
疲れたので、今回はここまでにしておきます。
また何かいいアイデアが浮かんだら、アップデートします。
コード全体
あえて冗長に書いているところもあります。
パフォーマンスは考えていません。
//+------------------------------------------------------------------+
//| ZigZaaag.mq5 |
//| Copyright 2024, Hailu |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Hailu"
#property link "https://www.mql5.com"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 1
#property indicator_type1 DRAW_SECTION
#property indicator_color1 clrWhiteSmoke
#property indicator_width1 2
input double ThinWidth = 50; // Thin width [%]
double bufZig[];
double bufLow[];
double bufHigh[];
datetime prevDt = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
SetIndexBuffer(0, bufZig, INDICATOR_DATA);
SetIndexBuffer(1, bufLow, INDICATOR_CALCULATIONS);
SetIndexBuffer(2, bufHigh, INDICATOR_CALCULATIONS);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
prevDt = 0;
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| 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[])
{
if (rates_total == 0)
{
ArrayInitialize(bufZig, 0);
ArrayInitialize(bufLow, 0);
ArrayInitialize(bufHigh, 0);
}
int preLoIdx = 0;
int preHiIdx = 0;
double preLo = low[0];
double preHi = high[0];
bufZig[0] = close[0];
bufLow[0] = close[0];
bufHigh[0] = close[0];
bool up = close[1] > close[0];
// 1分置きに再描画
if ((time[rates_total - 1] - prevDt) >= 60)
{
prevDt = time[rates_total - 1];
for (int i = 1; i < (rates_total - 1); i++)
{
double l = low[i];
double h = high[i];
// 上昇中
if (up)
{
// 転換
if (h < preHi)
{
// 高値確定
bufHigh[preHiIdx] = preHi;
// 安値更新
preLoIdx = i;
preLo = l;
up = false;
// 高値の起点を検索
double sv = low[preHiIdx];
int si = preHiIdx;
for (int j = preHiIdx - 1; j > 0; j--)
{
if (bufHigh[j] > preHi)
break;
if (bufLow[j] > 0)
{
if (bufLow[j] < sv)
{
sv = bufLow[j];
si = j;
}
else if (bufLow[j] > sv)
break;
}
}
double minw = (preHi - sv) * ThinWidth / 100;
// 高値を検索
int hi = si + 1;
while (hi < preHiIdx)
{
bool bf = false;
if (bufHigh[hi] > 0)
{
// 安値を検索
for (int j = hi + 1; j < preHiIdx; j++)
{
if (bufLow[j] > 0)
{
// 値幅が狭ければ、高値と安値を削除
if ((bufHigh[hi] - bufLow[j]) < minw)
{
bufHigh[hi] = 0;
bufLow[j] = 0;
}
hi = j + 1;
bf = true;
break;
}
}
// 不要なはずだけど、念のため
if (!bf)
break;
}
if (!bf)
hi++;
}
}
// 高値更新
else if (h > preHi)
{
preHiIdx = i;
preHi = h;
}
}
// 下降中
else
{
// 転換
if (l > preLo)
{
// 安値確定
bufLow[preLoIdx] = preLo;
// 高値更新
preHiIdx = i;
preHi = h;
up = true;
// 安値の起点を検索
double sv = high[preLoIdx];
int si = preLoIdx;
for (int j = preLoIdx - 1; j > 0; j--)
{
if (low[j] < preLo)
break;
if (bufHigh[j] > 0)
{
if (bufHigh[j] > sv)
{
sv = bufHigh[j];
si = j;
}
else if (bufHigh[j] < sv)
break;
}
}
double minw = (sv - preLo) * ThinWidth / 100;
// 安値を検索
int li = si + 1;
while (li < preLoIdx)
{
bool bf = false;
if (bufLow[li] > 0)
{
// 高値を検索
for (int j = li + 1; j < preLoIdx; j++)
{
if (bufHigh[j] > 0)
{
// 値幅が狭ければ、高値と安値を削除
if ((bufHigh[j] - bufLow[li]) < minw)
{
bufHigh[j] = 0;
bufLow[li] = 0;
}
li = j + 1;
bf = true;
break;
}
}
// 不要なはずだけど、念のため
if (!bf)
break;
}
if (!bf)
li++;
}
}
// 安値更新
else if (l < preLo)
{
preLoIdx = i;
preLo = l;
}
}
}
for (int i = 1; i < (rates_total - 1); i++)
{
if (bufLow[i] > 0)
bufZig[i] = bufLow[i];
if (bufHigh[i] > 0)
bufZig[i] = bufHigh[i];
}
}
return rates_total;
}
//+------------------------------------------------------------------+
コメント