English Русский 中文 Español Deutsch Português
オシレーターでZig Zagインジケータ - を作成してみましょう。技術課題の実施例

オシレーターでZig Zagインジケータ - を作成してみましょう。技術課題の実施例

MetaTrader 5 | 21 5月 2018, 08:29
4 325 0
Dmitry Fedoseev
Dmitry Fedoseev

目次

概論

インジケーターの開発を依頼するための要件定義を作成する方法"』の記事で、様々なオシレーターを使用したジグザグインジケーターの作成という技術課題の例を入れました。この記事では、この実装例を段階的に説明していきます。

オシレーターに基づくZig Zagインジケーター

ここでは課題の全文を掲載しないので、先に読み進める前に、上記のリンクから課題に目を通してください。  

課題の一般的分析

まず初めに開発されたインジケーターへの基本的な要件を記述します。

  1. 開発は段階的に行われること。
  2. インジケーターが可能な限り素早く動作するようにすること。
  3. インジケーターにグラフィックインターフェースがあること。

ジグザグアルゴリズム。ジグザグを構築するアルゴリズムは古典的なものと異なります。

  1. ジグザグは部分的な極値の形成時に方向転換をするのではなく、買われすぎまたは売られすぎのレベルのオシレーターの値が飛び出た時に方向を転換する。 
  2. ジグザグは価格チャート上で形成される。したがって、新しい極値は価格データに基づいて決まる。

つまり、これから記述するいくつかの特徴に注意を払う必要があります。 

  1. インジケーターの最大/最小値は、価格の最大/最小値と一致しないことがある。その為、ジグザグの方向転換時に、オシレーターが買われすぎ/売られすぎゾーンに出る少し前に、新しい価格の最大値/最小値が形成されなかったかチェックを行う必要がある。(図1参照)



    図 1. 矢印1があるバーの上でWPRが買われすぎゾーンへ突入しているが、
    新しいジグザグのセグメントは、矢印2の部分のバーまでに描かなければいけない。

  2. オシレーターに基づいてジグザグの方向転換が決まることから、その数値はバーの形成によって変化することになる。つまり、ジグザグが方向転換をすることはあるが、その後のバーの形成次第で方向転換が行われないこともある。このような場合には、インジケーターに正しく動作させる必要がある。
  3. 新しい極値が価格チャート(high/low値)によって決まることから、バーの形成次第で新しい最大値/最小値の形成が行われないこともある。しかし、新しい極値を持つバー上では、ジグザグが方向転換をすることもある。このような場合、新しい最大値/最小値は取り消される。(図2参照)


     
    図2. 1 — ジグザグの頂点は、バーによって形成された価格の最大値の部分にある。
    2 — ジグザグが方向転換し、その前に確定された最大値は取り消しされている。

    勿論、このようなケースについては、MetaTrader 5にはジグザグのカラー描画スタイルがあるので、議論の余地があります。これがあったら、確定された最大値より前に、ジグザグの頂点を左にずらすことなく、垂直のジグザグのセグメントにすることができたでしょう。しかし、このような描画の際には、ジグザグの2つのセグメント(垂直の物とそれに隣接する傾いているもの)を別々に着色することはできません。また、このようなジグザグの描画方法はそれほど一般的ではないため、もしこれが必要な場合は、それをタスクに明示する必要があるでしょう。つまり、最も一般的なオプションがデフォルトで選択されるということです。

表示。ジグザグの表示も独自の特徴を持っています。

  1. インジケーターの表示自体以外に、インジケーターが価格チャート上で、買われすぎゾーン(バーのhighレベル上の黄色の点)や売られすぎゾーン(バーのlowレベル上の緑の点)に入ったバーを、カラーの点でマーキングする必要があります。 
  2. パターンはジグザグの頂点と窪みの相互配置に基づいて決められます。パターンを形成するジグザグの部分は、他の色で色付けする必要があります。この着色が最も難しいです。第一に、パターンが発現したバーをただマーキングするだけでなく、履歴上のいくつかのジグザグのセグメントを再着色する必要があること。第二に、ジグザグの方向転換と新しい極値が、バーの形成次第で取り消しになることです。したがって、バーを計算する前に、パターンがマークされる可能性のあるジグザグの部分をきれいにする(よりニュートラルなカラーにする)必要があります。そして第三に、パターン(多方向のものを含む)が重なることがあることです。その為、ジグザグの部分を綺麗にし色付けしながら、以前に見つけたパターンの色とごちゃ混ぜにしてはいけません。(図3参照)



    図3.パターンの色付け

    図3に表示されているジグザグのセグメントを見てみましょう3.1から4までのセグメントが上昇トレンドのパターンを構成しています。つまり、セグメント1は青色にする必要があります。しかし、これより前では下降パターンの一部に入っていた為、すでに赤で着色されています。セグメント6の発生時には、もう一つの上昇パターン(セグメント3から6)が形成されています。各バーを計算する前にジグザグのセクションを初期の色に戻す必要がある為、今回のケースでは、セグメント3と4はすでに別のパターンに関係しているので、セグメントの5と6のみ色をクリアする必要があります。

    色付けの別の方法としては、新しいパターンごとにそのセグメントに含まれるすべての上昇セグメントの色を変えることです。しかしこのケースでは、インジケーターはそれほど有益にはなりません。したがって、遥かに複雑ではありますが、最初の方法を選ぶことにします。

グラフィックインターフェイス。グラフィックインターフェイスに必要な要件は比較的簡単なものです。管理要素のセットはフォーム上で一定ですが、これは選択したオシレーターに応じて変更する必要はありません。数値パラメータのうち、2つのみ使用され(買われすぎ/売られすぎレベル)、全てのオシレーターの為のセットと同じものです。

課題の特徴を理解したら、インジケーターの作成に取り掛かります。インジケーター作成の各ステップの終わりに、アプリケーションに対応するファイル名が表示されます。もしこの記事を読み進める中で、コードの追加方法を理解するのが困難と感じたら、エディタで対応する段階のファイルを開いて詳細を確認することをお勧めします。

ステップ 1 — ジグザグの構築

MetaEditorエディタで、OscZigZagStep1という名前の新しいカスタムインジケータを作成します。コード内に変数の場所をマークするために、1つの外部変数を追加します。イベントハンドラを選択するウィンドウで、最初のオプションであるOnCalculate(...,open,high,low,close)を選択します。他のハンドラは必要ありません。表示設定ウィンドウで2つのバッファを作成します。最初のバッファは『HighLow』という名前で、タイプはColor Arrowで、ゴールドとライムグリーンの二色にします。二つ目のバッファには名前は『ZigZag』で、タイプはColor Section、そしてグレー、コーンフラワーブルー、赤の三色にします。(図4)。


図4.インジケーター作成マスターのウィンドウでの表示パラメータの選択

カラーの点はバーに関連しているので、初めにこの点を描いて(バーの近くに)、それからジグザグを描くのが論理的です。したがって、バッファはこのように並んでいます。

『完了』ボタン押すとエディタでインジケーターファイルが開きます。まずここでindicator_color1の値を修正し、余分な色を削除します。indicator_color1の文字列は次のようになります。

#property indicator_color1  clrGold,clrLimeGreen

同様にindicator_color2のプロパティも修正する必要があります(3つの色を残す)。

自動的に作成された外部パラメータを持つ文字列を見つけましょう。

input int      Input1;

これを削除し、代わりにWPRインジケーターのパラメータの変数を宣言します。

input int         WPRperiod   =  14;
input double      WPRmax      =  -20;
input double      WPRmin      =  -80;

ハンドラ用の変数を少し下に宣言します。

int h;

OnInit()関数の冒頭でインジケーターがロードされます。

h=iWPR(Symbol(),Period(),WPRperiod);
if(h==INVALID_HANDLE){
   Print("Can't load indicator");
   return(INIT_FAILED);
}  

OnDeinit()関数でハンドラの解放を行います。

void OnDeinit(const int reason){
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
   }
}  

インジケーター作成マスターの使用時に表示バッファを作成しましたが、補助バッファも必要になります。たとえば、オシレーターの値の為のバッファがまさに必要です。indicator_buffersのプロパティを1単位増やします(4の代わりに6を入れます)。

#property indicator_buffers 5

バッファの為の配列がすでに宣言されている場所には、オシレーターの値の為にもう一つの配列を追加します。

double         wpr[];

OnInit()関数で中間計算用インジケーターバッファとしてこの配列が使用されることを明示します。OnInit()関数の末尾にコードを追加します。

SetIndexBuffer(4,wpr,INDICATOR_CALCULATIONS); 

OnCalculate()関数に移り、バーのカウント範囲の計算とバッファにWPRオシレーターの値をコピーする標準的なコードを書きます。

int start;

if(prev_calculated==0){
   start=0;
}
else{
   start=prev_calculated-1;
}

if(CopyBuffer(h,0,0,rates_total-start,wpr)==-1){
   return(0);
}

これで標準インジケーターサイクルを記録し、オシレーターが買われすぎ/売られすぎゾーンに入る場所に点を描くことができます。

for(int i=start;i<rates_total;i++){
   HighLowBuffer[i]=EMPTY_VALUE;
   if(wpr[i]>WPRmax){
      HighLowBuffer[i]=high[i];
      HighLowColors[i]=0;
   }
   else if(wpr[i]<WPRmin){
      HighLowBuffer[i]=low[i];
      HighLowColors[i]=1;      
   }      
}

この段階でインジケーターをチャートに貼り付けることができます。同様にWPRオシレーターを貼り付け、実行動作の正確性を検証します(図5参照)。


図5. 買われすぎ/売られすぎゾーンの価格チャート上での表示

ジグザグの作成に戻りましょう。いくつかの補助バッファが必要になります。一つはインジケーターの現在の方向用、後の二つはジグザグの最後の頂点と落ち込みを示すバーのインデックス用です。

double         dir[]; // 方向用
double         lhb[]; // 最後の頂点のバーのインデックス
double         llb[]; // 最後の落ち込みのバーのインデックス

私達は三つのバッファを追加したので、インジケーターのバッファ数を決定するプロパティを増やす必要があります。

#property indicator_buffers 8

OnInit()関数では、今宣言されたばかりの配列にSetIndexBuffer()関数を適用します。

SetIndexBuffer(5,dir,INDICATOR_CALCULATIONS);  
SetIndexBuffer(6,lhb,INDICATOR_CALCULATIONS);   
SetIndexBuffer(7,llb,INDICATOR_CALCULATIONS);    

このコードは、すでにOnInit()関数に存在する、SetIndexBuffer()関数の最後の呼び出しの直後に存在します。

ここが非常に重要なポイントです。Color Sectionタイプのバッファを正常に動作させるには、空の値0を指定する必要があります。さもないとチャート上でジグザグの代わりに、このような奇妙な線が出てきてしまいます。


図6. Color Sectionタイプのバッファに空の値を指定しないと、ジグザグの描画は正常に行われません

OnInit()関数の末尾に空の値を指定するには、returnの行の前から次の行を追加します。

PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); 

最初のパラメータの値が1であることに注意してください。このケースでは、表示されているバッファのグループのこのインデックス、つまり1はZigZagBuffer[]バッファに対応しています。

OnInit()関数はこれで準備完了です。ここでもう一度OnCalculate()関数に移って、標準インジケーターサイクルのコードの記入に戻ります。

インジケーターサイクルの始めに、HighLowBudderクリーンバッファのある最初の行の後に、補助バッファのデータを移動させます。

lhb[i]=lhb[i-1];      
llb[i]=llb[i-1];
dir[i]=dir[i-1];

買われすぎ/売られすぎゾーンにオシレーターが入るのを定義する私達のコードに、dir[]バッファへの方向設定を追加します。

if(wpr[i]>WPRmax){
   HighLowBuffer[i]=high[i];
   HighLowColors[i]=0;
   dir[i]=1;
}
else if(wpr[i]<WPRmin){
   HighLowBuffer[i]=low[i];
   HighLowColors[i]=1;      
   dir[i]=-1;
} 

さて、第一ステップの一番の山場-ジグザグの構築に移ります。ジグザグの方向が定義され、dir[]バッファでは上昇時は1、下降時に-1の値が入ります。また、どんなバーで方向を変えるかを決める必要があります。ジグザグ構築の基礎に、4つの枝に分かれる次のコードを作成します。

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // 下から上への方向転換
   }
   else{
      // 上方向への継続した動き
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){ 
      // 上から下への方向転換
   }
   else{
      // 下方向への継続した動き
   }      
}

ジグザグの下から上への方向転換と上方向への継続した動きの詳細を見てみましょう。二つの他の枝は対称になります。

上への方向転換

1. 最後の落ち込みから計算されたバーまでのバーの範囲(落ち込みのバーは範囲に入らない)で価格の最大値を探します。

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // 下から上への方向転換
      // 最大値の検索
      int hb=i;
      for(int j=i;j>llb[i];j--){
         if(high[j]>high[hb]){
            hb=j;
         }
      }
      //...
   }
   else{
      // 上方向への継続した動き
   }      
}

2. 検出したバーにジグザグの点をつけて、lhb[]バッファでこのバーのインデックスを指定し、ZigZagColorバッファを通じてニュートラルカラーを設定します。 

ZigZagBuffer[hb]=high[hb];
lhb[i]=hb;            
ZigZagColors[hb]=0;

3.このバーを再計算すると、オシレーターの値が変化して、ここに点があってはならない場合があります。その場合にはこれを削除します。通常これはインジケーターサイクルの始めに、バッファのクリーニングで行われます。

ZigZagBuffer[i]=0;

しかしこのケースでは形成されるジグザグの頂点は、未知数のバーの数を計算するバーから乖離します。(図1参照)したがって、新しい頂点があるバーのインデックスとカウントするバーの時間を保存する必要があります。

NewDotTime=time[i];
NewDotBar=hb;

変数NewDotTimeとNewDotBarはインジケーターのグローバルレベルで宣言されます。

4.インジケーターサイクルの冒頭で、計算されるバーに対応するNewDotTime変数の値をチェックします。もし一致がある場合は、ジグザグの新しい点を削除します。

if(NewDotTime==time[i]){
   ZigZagBuffer[NewDotBar]=0;  
}

上への動き

上昇を続ける動きを定義するコードのセクションを見てみましょう。もし次のバーのhighの価格が、前に固定したジグザグ値を超える場合、古い点を削除し新しいものを入れます。

if(high[i]>ZigZagBuffer[(int)lhb[i]]){ 
   // 古い点を削除
   ZigZagBuffer[(int)lhb[i]]=0;
   // 新しい点を入れる
   ZigZagBuffer[i]=high[i];
   ZigZagColors[i]=0;
   lhb[i]=i;
}

インジケーターサイクルの冒頭では、バーを再計算する前にインジケーターを初期化、つまり削除した点を元に戻す必要があります。

ZigZagBuffer[(int)lhb[i]]=high[(int)lhb[i]];
ZigZagBuffer[(int)llb[i]]=low[(int)llb[i]];  

インジケーターの初動で配列の境界を超えるエラーが起こらないようにするには、lhb[]とllb[]バッファの初期要素をゼロ値で初期化しなければなりません。また、NewDotTime変数とNewDotBar変数をリセットする必要がありますが、これは計算範囲の計算時に行われます。

if(prev_calculated==0){
   start=1;
   lhb[0]=0;
   llb[0]=0;   
   NewDotTime=0; 
}
else{
   start=prev_calculated-1;
}

これでインジケーター作成の第一ステップは終了です。このステージではOscZigZagStep1.mq5という名前のインジケーターをこの記事に添付します。

ステップ 2 — パターンの特定と色付け

パターンを明らかにするには、ジグザグの5つの頂点を比較する必要があります。インジケーターが迅速に動作するようにするために(これは主要な技術的要件でもあります)、インジケーター全体のサイクルで毎回これらの頂点を検索するのは望ましくはありません。新しい頂点が出現したら、これを別個の配列に格納した方が、より迅速にアクセスできるようになります。   

ジグザグの頂点に関するデータは構造体の配列に格納します。構造体には、bar index、value、directionのフィールドと、bool型のフィールドが1つ必要です。頂点がパターン内の最後のものである場合(ジグザグの着色を以前に認識されたパターンに限定する場合)は、こちらにtrue値が保存されます。構造体を記述し、配列を宣言します。

struct SZZDot{
   int bar;
   double val;
   int dir;
   bool pat;
};

SZZDot ZZDot[];

それから、AddZZDot()関数の呼び出しを、ジグザグを構築するコードの4つの部分のそれぞれの末尾に追加します。これは配列ZZDot []に新しいジグザグの頂点を追加します。

if(dir[i]==1){ 
   if(dir[i-1]==-1){          
      //...
      AddZZDot(1,high[hb],hb,i);
   }
   else{ 
      if(high[i]>ZigZagBuffer[(int)lhb[i]]){
         //...
         AddZZDot(1,high[i],i,i);
      }
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){
      //...
      AddZZDot(-1,low[lb],lb,i);
   }
   else{
      if(low[i]<ZigZagBuffer[(int)llb[i]]){
         //...
         AddZZDot(-1,low[i],i,i);
      }
   }      
}

関数AddZdot()に、方向、値、頂点を持つバーのインデックス、カウントするバーのインデックスの4つのパラメータが引き渡されます(関数自体は後ほど見ることにします)。検出した頂点の数(配列AADot []内の占有要素)に対しては、インジケーターバッファcnt []を使用します。配列cnt []を宣言すします。

double         cnt[];

OnInit()関数で、SetIndexBuffer()を呼び出します。

SetIndexBuffer(8,cnt,INDICATOR_CALCULATIONS);  

バッファ数を決定するプロパティの値を変更します。  

#property indicator_buffers 9

インジケーターサイクルの冒頭に、バッファで最後の値を移動します。

cnt[i]=cnt[i-1];

上でバーの計算中に明らかになったジグザグの方向転換が、同じバーの次の計算時に消えてしまう可能性があることについてすでにお話ししました。したがって、配列に格納されている頂点を削除する必要があります。しかし、この削除は配列を縮小するのではなく、占有している配列要素の数(cnt [] buffer)をカウントするカウンタを減らすことによって行われます。これにより、インジケーターの動作速度が大幅に向上します。

AddZdot()関数を見てみましょう。

void AddZZDot(int d,double v,int b,int i){
   
   int c=(int)cnt[i];

   if(c==0){ 
      // インジケーターの起動時または完全に再計算されたとき
      ArrayResize(ZZDot,1024);
      ZZDot[c].dir=d;
      ZZDot[c].val=v;
      ZZDot[c].bar=b;
      ZZDot[c].pat=false;
      cnt[i]=1;
   }
   else{
      if(ZZDot[c-1].dir==d){
         // 同じ方向の頂点を更新する
         ZZDot[c-1].val=v;
         ZZDot[c-1].bar=b;         
      }
      else{
         // 新しい頂点を追加する
         // 1024の要素のブロックで必要に応じて配列を増やす
         if(c>=ArraySize(ZZDot)){ 
            ArrayResize(ZZDot,c+1024);
         }
         // 新しい頂点を追加する
         ZZDot[c].dir=d;
         ZZDot[c].val=v;
         ZZDot[c].bar=b;
         ZZDot[c].pat=false;
         cnt[i]=c+1;
      }
   }
}

インジケーターの起動時または配列が完全に再計算されるとき、サイズは1024に設定され、その初期要素に頂点のパラメータが割り当てられ、頂点の数のカウンタが1増えます。次の関数の呼び出し時に、配列内の最後の頂点の方向がチェックされます。関数が呼び出されたパラメータに一致している場合は、最後の頂点のデータが更新されます。方向が反対の場合、新しい頂点が追加されます。 

上のタスクの分析の項目で、私はすでに、ジグザグの方向転換時に反対方向に向かう最後の頂点はより以前のバーに移されることがあると説明をしました。(図2参照)したがって、メインのジグザグのコードを実行する前に、ZZDot配列の最後にある要素を、既知の頂点の値にあらかじめ設定する必要があります。これは、インジケーターサイクルの冒頭に行われます。

if(cnt[i]>0){
   int ub=(int)cnt[i]-1;
   if(ZZDot[ub].dir==1){
      ZZDot[ub].bar=(int)lhb[i];
      ZZDot[ub].val=high[(int)lhb[i]];
   }
   else{
      ZZDot[ub].bar=(int)llb[i];
      ZZDot[ub].val=low[(int)llb[i]];         
   }
}

ここで、カウントしているバーの中で新しい頂点が検出された場合、その値はZZDot配列で更新され、方向転換があった場合には、以前に判明している頂点の値が残ります。

インジケーターの最初の計算の前、またそれを完全に再計算する前に、配列cnt []の最初の要素を初期化する必要があります。

if(prev_calculated==0){
   //...
   cnt[0]=0;
}
else{
   start=prev_calculated-1;
}

すべてのジグザグの頂点のデータを入れつつ、それらに簡単にアクセスできるようにするために、パターンの識別と色付けを行いましょう。これはジグザグの頂点が少なくとも5つある場合に行うことができます。

if(cnt[i]>=5)

頂点の配列の最後の要素のインデックスを計算します。

int li=(int)cnt[i]-1;

この頂点にはパターンが検出されていないことを示します。これはジグザグをニュートラルカラーに戻す為に必要になります。

ZZDot[li].pat=false;

ジグザグの部分を元の色に戻します。

for(int j=0;j<4;j++){
   if(ZZDot[li-j].pat){
      break;
   }
   ZigZagColors[ZZDot[li-j].bar]=0;
}

パターンのある頂点が検出されると、サイクルが終了することに注意してください。

パターンの条件を確認します。

if(ZZDot[li].dir==1){ // 上向き
   if(
      ZZDot[li].val>ZZDot[li-2].val && 
      ZZDot[li-2].val>ZZDot[li-4].val && 
      ZZDot[li-1].val>ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 
      // 色付け 
   }
}
else{ // 下向き
   if( 
      ZZDot[li].val<ZZDot[li-2].val && 
      ZZDot[li-2].val<ZZDot[li-4].val && 
      ZZDot[li-1].val<ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 		
      // 色付け                 
   }            
}

あとは色付けのコードを記述します。これはクリアのコードと同じようなものです。上向きの場合、   

for(int j=0;j<4;j++){
   if(j!=0 && ZZDot[li-j].pat){
      break;
   }
   ZigZagColors[ZZDot[li-j].bar]=1;
} 

クリアのコードとのちょっとした違いとしては、j = 0の時にサイクルからのループからアウトしない点にあります。

これで、インジケーター作成の第2ステップは終了です。インジケーターは次のようになります。 


図7. 第2ステップが終わった時点のインジケーターのようす。 

このステップではOscZigZagStep2.mq5という名前のインジケーターをこの記事に添付します。 

ステップ 3 — オシレーターの追加

リストは以下の通りです。

enum EIType{
   WPR,
   CCI,
   Chaikin, 
   RSI,
   Stochastic
};

オシレーターを選択するための外部変数を宣言しましょう。

input EIType               Type        =  WPR;

その他のオシレータのパラメータを追加します。

// CCI
input int                  CCIperiod   =  14;
input ENUM_APPLIED_PRICE   CCIprice    =  PRICE_TYPICAL;
input double               CCImax      =  100;
input double               CCImin      =  -100;
// Chaikin
input int                  CHfperiod   =  3;
input int                  CHsperiod   =  10;
input ENUM_MA_METHOD       CHmethod    =  MODE_EMA;
input ENUM_APPLIED_VOLUME  CHvolume    =  VOLUME_TICK;
input double               CHmax       =  1000;
input double               CHmin       =  -1000;
// RSI
input int                  RSIperiod   =  14;
input ENUM_APPLIED_PRICE   RSIprice    =  PRICE_CLOSE;
input double               RSImax      =  70;
input double               RSImin      =  30;
// Stochastic
input int                  STperiodK   =  5;  
input int                  STperiodD   =  3;
input int                  STperiodS   =  3;
input ENUM_MA_METHOD       STmethod    =  MODE_EMA;
input ENUM_STO_PRICE       STprice     =  STO_LOWHIGH;
input double               STmax       =  80;
input double               STmin       =  20; 

レベルの変数を宣言します。

double max,min;

OnStart関数の冒頭で、オシレーターを選択します。

switch(Type){
   case WPR:
      max=WPRmax;
      min=WPRmin;  
      h=iWPR(Symbol(),Period(),WPRperiod);      
   break;
   case CCI:
      max=CCImax;
      min=CCImin;  
      h=iCCI(Symbol(),Period(),CCIperiod,CCIprice);  
   break;      
   case Chaikin:
      max=CHmax;
      min=CHmin;  
      h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume);  
   break;          
   case RSI:
      max=RSImax;
      min=RSImin;  
      h=iRSI(Symbol(),Period(),RSIperiod,RSIprice);  
   break;   
   case Stochastic:
      max=STmax;
      min=STmin;  
      h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice);  
   break; 
}

if(h==INVALID_HANDLE){
   Print("Can't load indicator");
   return(INIT_FAILED);
}

OnCalculate()関数では、変数WPRmaxとWPminが変数maxとminに変更されます。 

これでインジケーター作成の第3ステップは終わりです。これで、インジケーターのプロパティウィンドウでオシレーターを選択できるようになりました。このステップではOscZigZagStep3.mq5という名前のインジケーターをこの記事に添付します。

ステップ 4 — グラフィックインターフェースの作成

グラフィカルインタフェースを作成する為に、IncGUIライブラリを使用します。このライブラリは、3部構成の『カスタムグラフィックコントロールパート1パート2パート3』の一連の記事で紹介されています。ライブラリの最新の改訂版(IncGUI_v4.mqh)は、GUIによる汎用的なオシレーターの記事に添付されています。そのアプリケーションはこの記事にも添付します。GUIでの作業を始める前に、IncGUI_v4.mqhファイルを端末データフォルダからMQL5 / Includesフォルダにコピーします。

ステップごとにグラフィックインターフェイスの作成プロセスを見ていきましょう。

ライブラリの接続。OscZigZagStep3という名前のインジケータOscZigZagStep3のコピーを作成し、そこにライブラリを接続します。

#include <IncGUI_v4.mqh>

フォームクラス。IncGUI_v4.mqhファイルには、CFormTemplateクラスがあります。これはフォームを作成するためのテンプレートの一種です。ライブラリを接続したら、すぐにそれをコピーしてインジケーターファイルに貼り付け、CFormTemplateからCFormに名前を変更します。

フォームのプロパティ。MainProperties()メソッドでは、フォームの基本プロパティを設定します。

m_Name         =  "Form";
m_Width        =  200;
m_Height       =  150;
m_Type         =  2;
m_Caption      =  "ZigZag on Oscillator";
m_Movable      =  true;
m_Resizable    =  true;
m_CloseButton  =  true;
  • 変数m_Nameは、フォームの名前(フォームを構成するすべてのグラフィックオブジェクトの接頭辞)です。
  • 変数m_Widthとm_Heightは、フォームの幅と高さをピクセル単位で表したものです。
  • m_Type変数はフォームのタイプです。値が2の場合、フォームの下部に閉じるボタンが表示されます。
  • m_Caption変数は、フォームのヘッダーです。
  • 変数m_Movableは移動可能なフォームで、フォームの左上隅には移動するためのボタンがあります。
  • 変数m_Resizableは、フォームを最小化/展開することができます。このためのボタンは右上隅にあります。
  • 変数m_CloseButtonは、フォームを閉じることができます。このボタンは右上隅にあります。

コントロール要素。フォームのコントロール要素を作成します。フォームには2つのフレームがあります。1つのフレームには、2つの入力フィールドのラジオボタンのグループがあります。フォームクラスのpublicセクションでコードを配置します。

CFrame m_frm1; // フレーム 1
CFrame m_frm2; // フレーム 2 
CRadioGroup m_rg; //ラジオボタンのグループ
CInputBox m_txt_max; //上のレベルのテキストフィールド
CInputBox m_txt_min; //下のレベルのテキストフィールド

コントロールの要素の初期化。OnInitEvent()メソッドで、コントロール要素を初期化します。

幅/高さー85/97のピクセル、『Osc Type』のヘッダー、幅44ピクセルのヘッダーの為の場所がある最初のフレームの初期化。

m_frm1.Init("frame1",85,97,"Osc Type",44);

このフレームには、ラジオボタンのグループが配置されます。

同じサイズで『Levels』のヘッダーと幅32ピクセルのヘッダーの為の場所がある二つ目のフレーム。

m_frm2.Init("frame2",85,97,"Levels",32);

このフレームにはレベルを入力するフィールドがあります。

ラジオボタングループの初期化。

m_rg.Init();

グループにラジオボタンを追加する。

m_rg.AddButton(" WPR",0,0);
m_rg.AddButton(" CCI",0,16);
m_rg.AddButton(" Chaikin",0,32);
m_rg.AddButton(" RSI",0,48);            
m_rg.AddButton(" Stochastik",0,64); 

上位レベルと下位レベルを入力するためのテキストフィールドの初期化。

m_txt_max.Init("max",45,-1," Max");
m_txt_min.Init("min",45,-1," Min");

両方のフィールドの幅は45ピクセルで、テキスト入力が可能(3番目のパラメータは-1)、これらのうちの1つには『Max』、2つ目のフィールドには "Min"と書かれています。

コントロール要素の表示。OnShowEvent()メソッドで、すべてのコントロール要素のShow()メソッドを呼び出し、フォーム上の座標を指定します。

m_frm1.Show(aLeft+10,aTop+10);
m_frm2.Show(aLeft+105,aTop+10);
m_rg.Show(aLeft+17,aTop+20);
m_txt_max.Show(aLeft+115,aTop+30);
m_txt_min.Show(aLeft+115,aTop+50);     

コントロール要素の非表示。OnHideEvent()メソッドでは、すべてのコントロール要素を非表示にします。

m_frm1.Hide();
m_frm2.Hide();            
m_rg.Hide();
m_txt_max.Hide();
m_txt_min.Hide(); 

フォームのヘッダー。異なるオシレーターを選択する場合は、フォームヘッダーに名称が表示されたほうが良いでしょう。その為、フォームクラスのPublicセクションにヘッダーのテキストを変更するメソッドを追加します。

void SetCaption(string str){
   m_Caption="ZigZag on "+str;
   ObjectSetString(0,m_Name+"_Caption",OBJPROP_TEXT,m_Caption);
}

フォームオブジェクトを作成する。CFormクラスのオブジェクトを作成します。

CForm form;

フォームのイベント。フォームとコントロール要素がユーザーの操作に応答するようにするには、インジケーターのOnChartEvent()関数からEvent()メソッドを呼び出す必要があります。イベントのタイプに応じて、このメソッドは異なる値を返します。フォームを閉じるは、1の値に対応していて、この時チャートからインジケーターを削除する必要があります。

if(form.Event(id,lparam,dparam,sparam)==1){
   ChartIndicatorDelete(0,0,MQLInfoString(MQL_PROGRAM_NAME)); 
   ChartRedraw();
}

コントロール要素のイベント。ラジオボタンのグループのイベントによって、インジケーターの変更が行われ、入力フィールドの値を変更するイベントによって、買われすぎ/売られすぎレベルが変更されます。どちらの場合も、インジケーターは完全に再計算されます。 

関数OnInit()でインジケーターの選択を行うコードの一部で、別の関数を入れます。

bool LoadIndicator(int aType){
   switch(aType){
      case WPR:
         max=WPRmax;
         min=WPRmin;  
         h=iWPR(Symbol(),Period(),WPRperiod);      
      break;
      case CCI:
         max=CCImax;
         min=CCImin;  
         h=iCCI(Symbol(),Period(),CCIperiod,CCIprice);  
      break;      
      case Chaikin:
         max=CHmax;
         min=CHmin;  
         h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume);  
      break;          
      case RSI:
         max=RSImax;
         min=RSImin;  
         h=iRSI(Symbol(),Period(),RSIperiod,RSIprice);  
      break;   
      case Stochastic:
         max=STmax;
         min=STmin;  
         h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice);  
      break; 
   }
   
   if(h==INVALID_HANDLE){
      Print("Can't load indicator");
      return(false);
   }   
   
   return(true);
   
}   

これは、インジケーターのOnInit()関数(一番始めに)とラジオボタンイベントから呼び出されます。OnInit()関数でインジケーターを選択した直後に、フォームを初期化し、コントロール要素に値を設定し、フォームを表示します。

if(!LoadIndicator(Type)){
   return(INIT_FAILED);
}

form.Init(1);
form.m_rg.SetValue(Type);
form.m_txt_max.SetValue(max);   
form.m_txt_min.SetValue(min);  
form.SetCaption(EnumToString(Type));
form.Show(5,20);

OnChartEvent()関数では、コントロール要素のイベントを処理します。インジケーターを変更するためのラジオボタンイベント。

if(form.m_rg.Event(id,lparam,dparam,sparam)==1){
   
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
      h=INVALID_HANDLE;
   }      
   
   if(!LoadIndicator(form.m_rg.Value())){
      Alert("Can't load indicator");
   }
   
   form.m_txt_max.SetValue(max);   
   form.m_txt_min.SetValue(min);    

   EventSetMillisecondTimer(100);
}

この時始めにIndicatorRelease()関数によってインジケーターハンドルが解放され、それから新しいインジケーターが選択され、新しい値が入力フィールドに設定され、タイマーが起動します。インジケーターが再計算される時に、データの更新エラーが発生する可能性があるため、タイマーの使用が必要になります。このような場合には、再計算が成功するまで試行を繰り返す必要があります。

レベルの変更。

if(form.m_txt_max.Event(id,lparam,dparam,sparam)==1 ||
   form.m_txt_min.Event(id,lparam,dparam,sparam)==1
){
   max=form.m_txt_max.ValueDouble();
   min=form.m_txt_min.ValueDouble();      
   EventSetMillisecondTimer(100);
}

入力フィールドのイベントによって、minおよびmax変数に新しい値が割り当てられ、タイマーが起動します。  

OnTimer()関数では、インジケーターが再計算されます。これが正常に行われた場合は、タイマーがオフになり、その後、目盛りに従って、インジケーターが通常通り動作します。インジケーターの再計算に必要なすべての処理は、上記で紹介した『GUIによる汎用的なオシレーター』という記事で詳説されています。したがって、ここでは根本的な違いのみに着目します。汎用的オシレーターは、価格データを必要としないクラスメソッドで計算されますが、ここでは、OnCalculate()関数を呼び出す必要があり、この関数に価格データを持つ配列を引き渡す必要があります。配列を宣言します。

datetime time[];
double open[];
double high[];
double low[];
double close[];
long tick_volume[];
long volume[];
int spread[];

バーの数を取得します。

int bars=Bars(Symbol(),Period());
      
if(bars<=0){
   return;
}

ジグザグを構築するには、すべての価格データが必要なわけではなく、time、high、lowの3つの配列だけが必要です。これらのみをコピーします。

if(CopyTime(Symbol(),Period(),0,bars,time)==-1){
   return;
}

if(CopyHigh(Symbol(),Period(),0,bars,high)==-1){
   return;
}      

if(CopyLow(Symbol(),Period(),0,bars,low)==-1){
   return;
} 

インジケーターのテスト時に、コピーされたデータの数がBars()関数で得られるバーの数よりも少ないことがあるという問題が発見されました。インジケーターバッファのサイズは、Bars()関数の値に対応しています。したがって、インジケーターを正しく表示するには、コピーされたデータで配列を増やし、データを末尾に移動する必要があります。

if(ArraySize(time)<bars){
   int sz=ArraySize(time);
   ArrayResize(time,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      time[j]=time[i];
   }   
}

if(ArraySize(high)<bars){
   int sz=ArraySize(high);
   ArrayResize(high,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      high[j]=high[i];
   }
}      

if(ArraySize(low)<bars){
   int sz=ArraySize(low);
   ArrayResize(low,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      low[j]=low[i];
   }
} 

OnCalculate()関数を呼び出します。

int rv=OnCalculate(
            bars,
            0,
            time,
            open,
            high,
            low,
            close,
            tick_volume,
            volume,
            spread
);

OnCalculte()関数が、エラーなく動作した場合は、タイマーをオフにします。 

if(rv!=0){
   ChartRedraw();     
   EventKillTimer();
   form.SetCaption(EnumToString((EIType)form.m_rg.Value()));
}

OnTimer()関数のフルコードと、完成したインジケーターは、添付のOscZigZagStep4.mq5ファイルで見ることができます。

チャートへインジケーターを設置する時に、コントロール要素を持つフォームが左上隅に表示されるはずです。(図8)


図8. ステップ4のグラフィックインターフェイス

まとめ

提示した技術的課題に従って、インジケーターの作成をデモンストレーションしました。ただし、パフォーマンスの正確さは動作の側面の1つに過ぎません。私たちの場合、このタスクはターミナルの機能やインジケーターの動作の特徴をよく知る、技術的に精通した専門家によって準備されたものです。しかし、それでも、課題のいくつかの点では、製作者と特にジグザグの色づけについて議論するのが非常に望ましいといえます。

パターンが特定されると、いくつかの以前のジグザグセグメントが再描画されるため、履歴上のインジケーターを調べるときに迷子になってしまう可能性があります。パターンを追加することができることで、視覚的分析がさらに困難になります。実際には、パターンというのは取引決定を下す為に必要なものです。この決断は、パターンが現われた時、または現れた後に下されることはあっても、その前に下されることはありません。したがって、ジグザグに色を付ける代わりに、パターンが現れたバーに矢印を描くという方法もあります。もう1つの解決策は、パターンが出現している間に、パターンの出現後から未来にかけてバーの列に水平線を描くことです。  

他にも、インジケーターを作成する過程で、最初のタスクの読み込み時や、ましてやコンパイル時にでさえ考えることができなかった、非常に意外で分かりにくい特徴が明らかになりました。私はジグザグの方向転換時に、ジグザグの最後の最大/最小値を削除する必要がある場合があることに注意をしていただきたいと思っています。この記事で触れたように、Color Zigzagバッファを使用することもできますが、このケースでは、Color ZigZagバッファセットには、2つのデータ用バッファと、1つのカラー用バッファがある為、色付けが困難になります。同じバー上で両方のデータバッファが値を持つ場合(垂直線がバーに沿っている場合)、カラーバッファに示された色は2つのジグザグセグメントに一度に割り当てられます。Color ZigZagではなく、ただのZigZagを使用して、ジグザグのセグメントはグラフィックオブジェクトで色付けするか、単に矢印か点を入れることもできます。一般的に、どのようなタスクでも、非常に注意深い読み取りと事前の議論は必要です。

アプリケーションファイル

すべてのファイルは、端末に配置する必要があるため、フォルダごとに格納されています。MQL5 /インジケーターフォルダには、インジケーター作成ステップに対応するファイルが格納されています。OscZigZagStep1.mq5、OscZigZagStep2.mq5、OscZigZagStep3.mq5、OscZigZagStep4.mq5です。

MQL5 / Includesフォルダには、OscZigZagStep4インジケーターでグラフィカルインターフェイスを作成するために必要なIncGUI_v4.mqhファイルが入っています。


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/4502

添付されたファイル |
MQL5.zip (87.98 KB)
グラフィカルインタフェースを通して最適化の結果を処理する グラフィカルインタフェースを通して最適化の結果を処理する
最適化結果の分析と処理についての話を展開していきます。今回の課題は、100の最良の最適化結果を選択し、それらをグラフィカルインタフェースの表に表示することです。ユーザーが最適化結果の表で列を選択しつつ、残高とドローダウンのマルチシンボルのグラフを別々に入手できるようにします。
自己キャッシング指標の速度比較 自己キャッシング指標の速度比較
本稿では、MQL5指標への古典的なアクセスと、代替のMQL4形式のアクセス法を比較します。指標へのMQL4形式のアクセスについては何種類かが考慮されます。MQL5コア内の指標ハンドルも考慮して分析されます。
任意の複雑さのレベルのグラフィカルなパネルを作成する方法 任意の複雑さのレベルのグラフィカルなパネルを作成する方法
この記事では、CAppDialog クラスに基づいてパネルを作成する方法と、パネルにコントロールを追加する方法について詳しく説明します。 パネルの構造とオブジェクトの継承を示すスキームを提供します。 この記事では、イベントの処理方法、および依存コントロールへの配信方法についても説明します。 その他の例では、サイズや背景色などのパネルパラメータを編集する方法を示します。
ディープニューラルネットワーク(その5)DNNハイパーパラメータのベイズ最適化 ディープニューラルネットワーク(その5)DNNハイパーパラメータのベイズ最適化
本稿では、様々な訓練の変形によって得られたディープニューラルネットワークのハイパーパラメータにベイズ最適化を適用する可能性について検討します。様々な訓練の変形における最適なハイパーパラメータを有するDNNの分類の質が比較されます。DNN最適ハイパーパラメータの有効性の深さは、フォワードテストで確認されています。分類の質を向上させるための方向性が特定されています。