English Русский 中文 Español Deutsch Português
Wolfe波動

Wolfe波動

MetaTrader 5 | 26 6月 2017, 09:27
2 491 0
Dmitry Fedoseev
Dmitry Fedoseev

内容

はじめに

Wolfe波動は、Bill Wolfe氏によって発見されて詳細に記述された視覚的分析パターンです。このパターンは三角形またはくさび形(Wolfe氏はそれを「上昇ウェッジ(rising wedge)」と呼んでいます)いくつかのニュアンスがあります。このBill Wolfe氏によって提案された視覚的手法は、市場参入の瞬間と方向を特定するためのパターンを検出することを可能にし、価格目標とその到達時間を予測するのに役立ちます。

本稿では、Wolfe波動の検出と解釈のルールを詳細に検討します。ユニバーサルジグザグ稿で記述されたジグザグ指標に基づいて、波動の自動検出と表示を受け持つ指標を作成し、それに基づいた簡単なエキスパートアドバイザーを追加します。このEAは指標の性能をテストし、Bill Wolfe氏が提案して本稿で説明される視覚分析の第一印象を得ることができます。 

Wolfe波動動を特定するためのルール

Wolfe波動を買いの例で考えてみましょう(図1)。価格は、2つの連続した下向きのより低いピーク(青い線、点1と点3)と2つの連続した下向きのより高いピーク(点2と点4)を形成します。反転と点4でのピークの形成後、価格は下落し続けます。価格が線1-3を形成すると、買い注文が出されます(点5)。 


図1 買いでのWolfe波動:青い線は価格、赤い線は目標を決めるために形成され、参入は点5で実行されてターゲットは点7

線1-3と線2-4の交点にある点6は目標到達時間を特定します。目標値(点7)は、線1-4と点6を通る垂線との交点として定義されます。この方法論は逆指値計算アルゴリズムを提供していません。一般的なアドバイスは自らの裁量でSLを使用することです。Wolfeの本での波動を特定するルールは、上記で全部です。 

本稿の指標を開発する際には、もう少しルールがあります。

  1.  点3は点1より著しく低く、次の条件が確認される必要があります。

    v3<v1-d1

    ここで

    • v3 — 点3のレベル
    • v1 — 点1のレベル
    • d1 — 点1と点2の間の垂直距離(線分1 "-2")にK1を乗算(K1はプロパティウィンドウのパラメータで、デフォルト値は0.1)したもの

  2. ターゲットを決める線1-4は、上向きである必要があります。つまり、点4は点1より著しく高くなければなりません。次の条件を確認する必要があります。

    v4>v1+d1;

    v4 — 点4のレベル 

  3. 点4は点2より著しく低く、次の条件が確認される必要があります。

    v4<v2-d2;

    ここで、v2は点2のレベル、d2は点2と点3の間の垂直距離(線分2 "-3")にK2を乗算したものです(K1はプロパティウィンドウからのパラメータで、デフォルト値は0.1)。

  4. 目標到達時間を決定する線2-4と線1-3は右で交差しなければならないので、2-2 'は4-4'よりも著しく高くなければなりません。ここでは次が確認されます。

    h2-h4>K3*h2;

    ここで、h2は線分2-2 'の高さ、h4は線分サイズ、K3は比率です(K3はプロパティウィンドウのパラメータで、デフォルト値は0.1)。

これらの明確なルールは絶対的に正しいわけではありません。指標作成過程についてはのちに詳しく説明するので、 この資料に基づいて、自分の考えに従ってコードを調整することができるでしょう。 

使用するジグザグの選択

始める前に、不変なジグザグ稿の添付ファイルをダウンロードしてください。これにはさまざまなバージョンのジグザグ指標が含まれています。本稿で使用するには、このうちの1つを選択する必要があります。iUniZigZagPriceとiUniZigZagPriceSWは、チャートで実行される他の指標に基づいたジグザグ計算用に設計されており、視覚的解析にのみ役立つので使用しません。他の指標はもっと面白いようで、それぞれを使用してエキスパートアドバイザーを作成することができます。iCloseZigZagとiHighLowZigZagも単にZigZagを作成する最初の例なので使用しません。残っているのはiUniZigZagとiUniZigZagSWの2つのバージョンです。サブウィンドウで動作するiUniZigZagSW指標はより広い機能を提供するので、ここではより適しています。添付ファイルにはiUniZigZagSWEvents指標も含まれています。この指標は、iUniZigZagSW指標にアクセスするときにiCustom() 関数を使用する例を示しています。iUniZigZagSW指標のすべての可能性が使用され、さらにWolfe波動特定コードをジグザグコードから分離することができるので、このバージョンを使用することにします。

iUniZigZagSWEvents指標は価格表に表示され、指標を描画するには4つのバッファが使用されます。これは、矢印のための2つと点のための2つで、Wolfe波動を特定するために必要です。矢印はパターンの識別場所を示し、点はターゲットに使用されます。我々の指標は、グラフィックオブジェクト、特にトレンドラインを使用してターゲットを特定する波動と形を描画します。レイを延長せずに線分として描画すると、これはさまざまな形を表示するための非常に便利なツールとなります。  

Wolfe波動は参入の瞬間と方向を特定することに加えて、ターゲットの予測にも使用されます。したがって、iUniZigZagSWを使用する際に問題が発生します。この指標にはSrcSelectパラメータがあり、どちらのジグザグが描画されたかに基づいて解析データのソースを選択できます。選択できるのは次の4つのオプションのいずれかです。

  • Src_HighLow — 高値と安値
  • Src_Close — 終値
  • Src_RSI — RSI指標
  • Src_MA — 移動平均

取引のためのエキスパートアドバイザーは、今作成している指標に基づいて作成されます。そのため、価格を使用してジグザグを構築すると、予測されたターゲットを決済指値に使用することができます。チャートにターゲットを表示するのに問題はありません。しかし、RSI(SrcSelect=Src_RSI)を使用してジグザグを計算すると、予想されるターゲットは価格ではなくRSI指標のためのものとなります。したがって、RSI指標が目標値に達した場合は市場価格で決算する必要があり、目標価格と追加形をチャートに表示することは不可能になります。

価格(Src_HighLowまたはSrc_Close)に基づいて描かれたジグザグを使用すると、目標価格および追加形がチャートに表示されます。それ以外の場合は、矢印のみが表示され、検出された形とその方向が表示されます。目標値は引き続き適切な価格バッファーには存在します(他の目的のためにエキスパートアドバイザーで市場価格で決算するため)が、表示されません。

ほとんどの場合、指標が目標レベルに達した場合に市場価格で決済するという考え方は実際には実装できません。ほとんどの指標値は一定の範囲内で変化し、目標はこの範囲外となるからです。ただし、バッファにはいずれの場合もターゲット値が格納されます。

ジグザグピークに関するデータの収集

指標の作成に取り掛かりましょう。iUniZigZagSWEventsファイルをエディタで開いてiWolfeWavesとして保存します。 この指標で作業します。

すべてのジグザグのピークに直接アクセス出来れば非常に便利でしょう。そうすれば、毎回履歴で検索する必要はありません。値を格納するための配列を作成しましょう。ジグザグが方向を変えるたびに、新しい要素が配列に追加されます。指標が単に最後の線分を延長する場合(極値が更新される場合)、配列の最後の要素が更新されます。

各ピークについて、ピーク値、方向、およびピークが位置するバーのインデックス(左から右へのインデックス付け)を保存します。 これには、3つのフィールドを持った構造体を使います。

struct SPeackTrough{
   double   Val; // 値
   int      Dir; // 方向
   int      Bar; // バーのインデックス
};

これらの構造体の配列を作成しましょう。

SPeackTrough PeackTrough[];

高値と安値に基づくジグザグ(SrcSelect = Src_HighLow)のみを使用した場合、方向を変更するには配列を大きくして値を設定し、最後の要素を最後の指標線分の延長によって更新すれば十分です。終値(SrcSelect = Src_Close)または他の指標のデータに基づくジグザグでは、これはより困難となります。ジグザグは、反転が起こった時点でのバーの形成中に、(現在のバーが開く前の)元の状態に戻ることがあるからです。つまり、同じバーの新しい計算ごとに、ピークの配列を、その配列が前のバーであった初期状態に戻す必要があります。配列のサイズを頻繁に変更すると、指標操作が遅くなり得るので、使用される配列のサイズが格納される追加の変数を紹介します。配列は必要に応じてブロック単位で変更され、サイズの増加のみが可能です。同じバーを再度カウントする前に、この変数の初期値を返します。

配列のサイズは2つの変数を使用して格納されます。そのうち1つには前のバーの配列サイズが格納され、あと1つには現在計算されているバーのサイズが格納されます。

int PreCount; // 前のバーでのPeackTrough配列のサイズ
int CurCount; // 現在計算されているバーでのPeackTrough配列のサイズ

バーの作成と計算が完了した後、または履歴バーを計算した後は、CurCount変数の値をPreCount変数に移さなければなりません。その後、新しい成形バーの各計算の前に、値をPreCountからCurCountに移動します。すべての計算では、CurCount変数のみが使用されます。PreCount変数は補助的です。バーの形成の完了に関する情報は、ネットバーが開くとき(または、履歴の次のバーの計算に切り替えるとき)のみに入ります。新しいバーの出現は時間によって決定されます。バーの時間が変更された場合(または、履歴の次のバーの計算が開始された場合)、新しいバーが現れます。新しいバーを決定するには、補助変数が必要です。

datetime LastTime;

PreCount、LastCount、LastTimeは指標のグローバル変数ですが、OnCalculate()指標関数で静的変数として宣言することもできます。 

OnCalculate()関数に移りましょう。第1の指標計算が実行されるか、または新しいバーのみが計算されるかはprev_calculatedの値に基づいて決定されます。0は完全な計算を意味します。この場合は、PreCount、CurCount、LastTime変数の初期化が必要です。次のコードはOnCalculte() 関数の冒頭にあって、計算のためのバーの範囲を定義して補助変数を初期化します。

int start; // 最初の計算されたバー

if(prev_calculated==0){ // すべてのバーを完全に計算
   start=1;
   CurCount=0;   
   PreCount=0;
   LastTime=0;
}
else{ // 新しいバーの計算
   start=prev_calculated-1;
}

さて、標準的な指標ループを見てみましょう。初めに、PreCountとCurCount変数に値を転送します。

for(int i=start;i<rates_total;i++){

   if(time[i]>LastTime){ // 新しい(次の)バーの最初の計算
      LastTime=time[i];
      PreCount=CurCount;
      PreDir=CurDir;
   }
   else{ // バーの再計算
      CurCount=PreCount;
      CurDir=PreDir;
   }


すべての計算では、CurCount変数のみが使用されてPreCountはCurCountの現在の値を維持するように設計されています。新しいバーが開かれるとき、CurCountには初めは前のバーの計算後に得られた値が含まれているため、この値をPreCountに移動します。CurCountの値は新しいバーの計算後に変更されますが、その値は次のバーが開くときのみに確定できるため、同じバーを再計算する場合は、PreCount変数の値がCurCountに置かれます。

指標のメインループには、次のiUniZigZagSWEvents指標から取られたコードを含める必要があります。

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;

UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;      

// 方向

double dir[2];
if(CopyBuffer(handle,3,rates_total-i-1,2,dir)<=0){
   return(0);
}
if(dir[0]==1 && dir[1]==-1){
   DnArrowBuffer[i]=high[i];
   c++;

}
else if(dir[0]==-1 && dir[1]==1){
   UpArrowBuffer[i]=low[i];
   c++;
}

// 新しい高値

double lhb[2];
if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){
   return(0);
}
if(lhb[0]!=lhb[1]){
   UpDotBuffer[i]=high[i];
}

// 新しい安値

double llb[2];
if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
   return(0);
}
if(llb[0]!=llb[1]){
   DnDotBuffer[i]=low[i];
}  

コードの矢印を描画する部分は使用されないので削除します。

操作中、指標は方向変更と最後の線分の各延長を含むジグザグの各変化を監視します。これは最後の線分が点5の特定に必要であるためです(図1参照)。上のフラグメントのコードの一部を使用します。これは新しい極値の描画に関連しています。

ジグザグの方向を監視してその変化を判断するには、CurCountとPreCountに似たPreDir変数とCurDir変数が必要です。

int PreDir; // 前のバーのジグザグの方向
int CurDir; // 現在計算されたバーのジグザグの方向

これらはグローバル変数でもOnCalculate()での静的変数でも可能です。これらの変数は、指標計算の開始時に初期化され、PreCountやCurCountと同様、バーの計算後には値が移動される必要があります。以下は、指標作成の現在のステップとしての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[])
  {

   int start; // 計算を開始するバーを表す変数
   
   if(prev_calculated==0){ // 完全な計算
      start=1; 
      CurCount=0;
      PreCount=0;
      CurDir=0;
      PreDir=0;      
      LastTime=0;
   }
   else{ // 新しいバーのみの計算
      start=prev_calculated-1;
   }

   // 指標のメインループ
   for(int i=start;i<rates_total;i++){
   
      if(time[i]>LastTime){ // 新しいバー
         LastTime=time[i];
         PreCount=CurCount;
         PreDir=CurDir;
      }
      else{ // バーの再計算
         CurCount=PreCount;
         CurDir=PreDir;
      }

      // 矢印と点を描くバッファの消去
      
      UpArrowBuffer[i]=EMPTY_VALUE;
      DnArrowBuffer[i]=EMPTY_VALUE;
      
      UpDotBuffer[i]=EMPTY_VALUE;
      DnDotBuffer[i]=EMPTY_VALUE;    
      
      // 補助変数
      
      double hval[1];
      double lval[1];
      
      double zz[1];
      
      // 新しい高値
      
      double lhb[2];
      // 新しい高値のバーのインデックスを持つバッファの2つの要素を受け取る
      if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){ 
         return(0);
      }
      if(lhb[0]!=lhb[1]){ // 新高値が存在する
         // 高値(または、ジグザグ計算が基づくデータの値)を取得する
         if(CopyBuffer(handle,0,rates_total-i-1,1,hval)<=0){
            return(0);
         }      
         if(CurDir==1){ // 最後に知られている上向きの方向
            //最後のジグザグの点に関する情報を更新する
            RefreshLast(i,hval[0]);
         }
         else{ // ジグザグの方向が変わった
               // 新しい値を追加する
            AddNew(i,hval[0],1);
         }
         //下向きのWolfe波動を認識するためのチェック条件を追加する  
      }
      
      // 新しい安値
      
      double llb[2];
      // 新しい安値のバーのインデックスを持つバッファの2つの要素を受け取る
      if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
         return(0);
      }
      if(llb[0]!=llb[1]){ // 新安値が存在する
         // 安値(または、ジグザグ計算が基づくデータの値)を取得する
         if(CopyBuffer(handle,1,rates_total-i-1,1,lval)<=0){
            return(0);
         }         
         if(CurDir==-1){ // 最後に知られている下向きの方向
            //最後のジグザグの点に関する情報を更新する
            RefreshLast(i,lval[0]);
         }
         else{ // ジグザグの方向が変わった
            // 新しい値を追加する
            AddNew(i,lval[0],-1);
         }
         //上向きのWolfe波動を認識するためのチェック条件を追加する 
      }      
   }   
   return(rates_total);
}

このコードには、AddNew()関数とRefreshLast()関数が含まれます。ジグザグが変更されたバーのインデックスと新しい極値は両方の関数に渡されます。ジグザグの方向はさらにAddNew()に渡されます。

下記は新しい点を追加するためのAddNew()関数です。

void AddNew(int i,double v,int d){
   if(CurCount>=ArraySize(PeackTrough)){ // 配列に空き要素がない
      ArrayResize(PeackTrough,ArraySize(PeackTrough)+1024); // 配列サイズの増加
   }
   PeackTrough[CurCount].Dir=d; // 方向の設定
   PeackTrough[CurCount].Val=v; // 値の設定
   PeackTrough[CurCount].Bar=i; // バーの設定
   CurCount++; // 占められている配列要素の数で変数を増やす
   CurDir=d; // 最後のジグザグの方向を記憶する
} 

下記は最後の点をレフレッシュするためのRefreshLast()関数です。

void RefreshLast(int i,double v){
   PeackTrough[CurCount-1].Bar=i; // 新しいバーの設定
   PeackTrough[CurCount-1].Val=v; // 新しい値の設定
} 

指標はこの段階で保存され、さまざまなジグザグパターンを定義する指標の開発の基礎として使用されることができます。以下の添付ファイルでの指標銘は"iWolfeWaves_Step_1"です。

幾何学について少し

幾何学は、Wolfe波動を特定する際にもターゲットを決めるための形を追加する際にも必要です。これらの問題を別々に見て、解決するための関数を書くことにしましょう。

問題#1:直線が点のペアx-yによって設定されます。ここで、xはバーのインデックスでyは値(価格または指標値)です。3番目の点のx座標はわかるので、この点での線の値を見つける必要があります(図2)。


図2 X1, Y1, X2, Y2, X3が与えられた場合にY3を求める

問題#1の答え:X軸に沿った1単位の増加あたりのY軸に沿った線の増加の値を決定します。

D=(Y2-Y1)/(X2-X1)

ここで、Dは増分、Y1は点1の価格または指標値、Y2は点2の価格または指標値、X1は点1のバーインデックス、X2は点2のバーインデックスです。  

Y3の決定:

Y3=Y1+(X3-X1)*D

ここで、X3は点3のバーインデックス、Y3は求められた点3での線の値です。

得られるのは次の関数です。

double y3(double x1,double y1,double x2,double y2,double x3){
   return(y1+(x3-x1)*(y2-y1)/(x2-x1));
}

以下のパラメータが関数に渡されます。

  • x1 — 点1でのバーインデックス
  • y1 — 点1での値
  • x2 — 点2でのバーインデックス
  • y2 — 点2での値

問題#2:2つの線が2点x-yによって設定されます。交点のx座標を求める必要があります(図3)。なぜここでx座標を選んだのかという疑問がわくかもしれませんが、いずれにせよ、x座標を取得した後、点3のy座標が(線の1つの式を使用して)計算されます。したがって、まず点3のy座標を求め、次に方程式を使ってx値を求めることができます。  


図3 2つの線が与えられ、それらの交点を求める


まず、2点の座標を用いて、y=a+b*xの形での線形方程式を得ます。

事前計算を実行しましょう。下記が線の勾配値(x軸に沿った1単位あたりのy軸に沿った単位)です。

D1=(Y12-Y11)/(X12-X11)

ここで、D1は1番目の線の所望の勾配値(バー当たりの線の値の変化)、X11は1番目の線の点1におけるバーインデックス、X12は1番目の線の点2におけるバーインデックス、Y11は1番目の線の点1での値、Y12は1番目の線の点2での値です。     

2番目の線の勾配値は下記です。

D2=(Y22-Y21)/(X22-X21)

ここで、D2 は2番目の線の所望の勾配値(バー当たりの線の値の変化)、X21は2番目の線の点1におけるバーインデックス、X22は2番目の線の点2におけるバーインデックス、Y21は2番目の線の点1での値、Y22は2番目の線の点2での線の値です。

下記は線の方程式です。 1番目の線の方程式:

Y3=Y11+D1*(X3-X11)

ここで、Y3は交点(点3)における線の値で、X3は点3におけるバーインデックスです。

2番目の線の方程式:

Y3=Y21+D2*(X3-X21)

これらの線の値は交点では等しいため、1番目の方程式を2番目の方程式と等しくします。

Y11+D1*(X3-X11)=Y21+D2*(X3-X21);

得られた式を用いてX3を求めます。その結果、交点のX座標を特定するTwoLinesCrossX() 関数が得られます。

double TwoLinesCrossX(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(y22-y21)/(x22-x21);
   double k1=(y12-y11)/(x12-x11);
   return((y11-y21-k1*x11+k2*x21)/(k2-k1));
}

以下のパラメータが関数に渡されます。

  • x11 — 1番目の線の点1でのバーインデックス
  • y11 — 1番目の線の点1での値
  • x12 — 1番目の線の点2でのバーインデックス
  • y12 — 1番目の線の点2での値
  • x21 — 2番目の線の点1でのバーインデックス
  • y21 — 2番目の線の点1での値
  • x22 — 2番目の線の点2でのバーインデックス
  • y22 — 2番目の線の点2での値

線の交点のx座標が定義されると、y座標は、1番目の線の2点の座標と問題#1を解決するときに得られた関数y3()を使って計算することができます。

最初にy座標を取得しなければならない場合は、線方程式を変換してx座標をyに代入します。1番目の線の方程式は

X3=X11+(Y3-Y11)/D1

で2番目の線の方程式は

X3=X21+(Y3-Y21)/D2

なので、2つの式を等号で結びます。

X11+(Y3-Y11)/D1=X21+(Y3-Y21)/D2

上記の式に基づいてY3を求めましょう。これによってTwoLinesCrossY()関数が得られます。

double TwoLinesCrossY(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(x22-x21)/(y22-y21);
   double k1=(x12-x11)/(y12-y11);
   return((x11-x21-k1*y11+k2*y21)/(k2-k1));
}

この関数のパラメータはTwoLinesCrossX()のパラメータと同じです。 

波動動の特定

すべてのジグザグピークと補助的な幾何学関数に簡単にアクセスできるようになったので、Wolfe波動の特定に進むことにします。最後のジグザグ線分が線1-3、すなわち点5を横切る瞬間を「把握」する(図1参照)ためには、新しいジグザグ極値が現れるたびに(方向が変わったときと最後の線分が延長されたときの両方)Wolfe波動の条件を確認します。条件を確認する必要があるすべての場所は、上記のOnCalculate() 関数コードで詳細にコメントされています。CheckDn()関数とCheckUp()関数はそれらから呼び出されます。そのうちの1つであるCheckUp() 関数について詳しく説明しましょう。

void CheckUp(int rates_total,const double & low[],const datetime & time[],int i){

   if(CurCount<5 || CurDir!=-1){ 
      // ピークが足りない場合やジグザグが下向きでない場合は確認しない
      return;
   }   
   
   // ピークデータで短い変数を作成する 

   // ピーク値を持つ変数
   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   // ピークバーを持つ変数
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
                  
   if(CurLastBuySig!=i4){ // このジグザグ構成で波動動が検出されなかった場合
      double d1=K1*(v2-v1); // ピーク1に対するピーク3のインデントの最小値
      if(v3<v1-d1){ // ピーク3はピーク1よりも著しく低い
         if(v4>v1+d1){ // 線1-4が上向きである
            double d2=K2*(v2-v3); // ピーク2に対するピーク4のインデントの最小値
            if(v4<v2-d2){ // ピーク4はピーク2よりも著しく低い
               double v5l=y3(i1,v1,i3,v3,i); // 点5の値
               if(v5<v5l){ // 最後のジグザグ線分が線1-3を交差した
                  double v4x=y3(i1,v1,i3,v3,i4); // 点4'での値
                  double v2x=y3(i1,v1,i3,v3,i2); // 点2'での値
                  double h4=v4-v4x; // 高さ4-4'
                  double h2=v2-v2x; // 高さ2-2'
                  if(h2-h4>K3*h2){ // 線1-3と線2-4が会う
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // 線1-3と線2-4が交差したバー
                     double tv=y3(i1,v1,i4,v4,tb); // 線1-3と線2-4が交差した値
                     UpArrowBuffer[i]=low[i]; // 上矢印の表示
                     UpDotBuffer[i]=tv; // ターゲットレベルでの点の表示
                     CurLastBuySig=i4; // このジグザグ構成では形状が見つかったことを記憶する
                     if(_DrawWaves){ // 形状と形の描画
                        DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

波動を特定するためには少なくとも5つのジグザグピークが必要です。追加条件は、後で上向きとなる波動を特定するにはジグザグは下向きでなければならないということです。

if(CurCount<5 || CurDir!=-1){ 
   // ピークが足りない場合やジグザグが下向きでない場合は確認しない
   return;
}   

ピークデータの取得にはPeakTrough配列を直接参照することができますが、これよりは名前の短い補助変数を使用する方が便利で簡単です。

// ピーク値を持つ変数
double v1=PeackTrough[CurCount-5].Val;
double v2=PeackTrough[CurCount-4].Val;
double v3=PeackTrough[CurCount-3].Val;
double v4=PeackTrough[CurCount-2].Val;
double v5=PeackTrough[CurCount-1].Val;
   
// ピークバーを持つ変数
int i1=PeackTrough[CurCount-5].Bar;
int i2=PeackTrough[CurCount-4].Bar;               
int i3=PeackTrough[CurCount-3].Bar;
int i4=PeackTrough[CurCount-2].Bar;
int i5=PeackTrough[CurCount-1].Bar;

波動が検出されて矢印が設定された場合、同じジグザグ構成を使用する必要はなくなります。ジグザグ構成は、ピーク4のインデックス(最後に形成されたピーク)を確認することによって識別されます。

if(CurLastBuySig!=i4){ // このジグザグ構成で波動動が検出されなかった場合

構成IDの値を格納するには、CurCountとPreCountに似た変数のペアを使用します。

今すぐ波動の特定に進みます。点1に対する点3の最小シフトの値と点1に対する点2のシフトを計算します。

double d1=K1*(v2-v1); // ピーク1に対するピーク3のインデントの最小値


点のシフトを確認します。

if(v3<v1-d1){ // ピーク3はピーク1よりも著しく低い
   if(v4>v1+d1){ // 線1-4が上向きである

点2に対する点4のインデントの最小値を計算します。

double d2=K2*(v2-v3); // ピーク2に対するピーク4のインデントの最小値

点2と点4の位置を確認します。

if(v4<v2-d2){ // ピーク4はピーク2よりも著しく低い

次に、計算されたバーに対応する、線1-3に位置する点の値を計算しましょう。

double v5l=y3(i1,v1,i3,v3,i); // 点5の値

線1-3が触れられたかどうかを確認します。

if(v5<v5l){ // 最後のジグザグ線分が線1-3を交差した

点4 'と点2'の値を計算します。

double v4x=y3(i1,v1,i3,v3,i4); // 点4'での値
double v2x=y3(i1,v1,i3,v3,i2); // 点2'での値

高さ4-4' と高さ2-2'を計算します。

double h4=v4-v4x; // 高さ4-4'
double h2=v2-v2x; // 高さ2-2'

これらの高さを使って、線1-3と線2-4が右で会うかどうかを確認します。

if(h2-h4>K3*h2){ // 線1-3と線2-4が会う

この条件が満たされれば、波動が見つかったことになります。 

ターゲットを定義します。初めに定義するのはターゲットバーです。

double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // 線1-3と線2-4が交差したバー


計算の精度のために 'double'変数が使用されていることにご注意ください。     

下記はターゲット値です。

double tv=y3(i1,v1,i4,v4,tb); // 線1-3と線2-4が交差した値

 アイコンを表示してジグザグ構成IDを「記憶」します。

UpDotBuffer[i]=tv; // ターゲットレベルでの点の表示
CurLastBuySig=i4; // このジグザグ構成では形状が見つかったことを記憶する

最後に、波動を描いて、目標を特定するための形を描きます。

if(_DrawWaves){ // 形状と形の描画
   DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
}

波動と形の描画(DrawObjects()関数)は、本稿の別のセクションで検討されます。 

下向きの波動(売り用)はCheckDn関数によって特定されます。これは方向に関連した小さな違いを除いてCheckUpと同じです。関数コードとCheckUp()関数との相違点は以下のとおりです。

void CheckDn(int rates_total,const double & high[],const datetime & time[],int i){

   // ピークが足りない場合や上向きでない場合 
   if(CurCount<5 || CurDir!=1){ 
      return;
   }

   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
   
   if(CurLastSellSig!=i4){               
      double d1=K1*(v1-v2); // ピークv1がピークv2より高い
      if(v3>v1+d1){ // ピークv3がピークv1より高い
         if(v4<v1-d1){ // ピークv4がピークv1より低い
            double d2=K2*(v3-v2); // ピークv3がピークv2より高い 
            if(v4>v2+d2){ // ピークv4がピークv2より高い
               double v5l=y3(i1,v1,i3,v3,i);
               if(v5>v5l){ // ジグザグが1-3を上向きに破る
                  double v4x=y3(i1,v1,i3,v3,i4);
                  double v2x=y3(i1,v1,i3,v3,i2);
                  double h4=v4x-v4; // 点4'が点4より高い
                  double h2=v2x-v2; // 点2'が点2より高い
                  if(h2-h4>K3*h2){   
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4);
                     double tv=y3(i1,v1,i4,v4,tb);                              
                     DnArrowBuffer[i]=high[i];
                     DnDotBuffer[i]=tv;
                     CurLastSellSig=i4;   
                     if(_DrawWaves){
                        // 他の色での描画
                        DrawObjects(SellColor,
                                    SellTargetColor,
                                    v1,
                                    v2,
                                    v3,
                                    v4,
                                    v5l,
                                    i1,
                                    i2,
                                    i3,
                                    i4,
                                    i5,
                                    time,
                                    i,
                                    tb,
                                    tv,
                                    rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

最初の違いは、初期のチェックにあります。

// ピークが足りない場合や上向きでない場合 
if(CurCount<5 || CurDir!=1){ 
   return;
}

ピークが足りない場合やジグザグが下向きでない場合は、関数での操作は終了します。

下向きの場合、ピークと谷が交替します。点1、3、5、6が上にあり、点2、4、7が下にあるため、数式内のいくつかの変数の場所も変更されます。ピーク1とピーク3及びピーク1とピーク4の間の最小距離の決定:

double d1=K1*(v1-v2); // ピークv1がピークv2より高い

ピーク1とピーク3の位置の確認:

if(v3>v1+d1){ // ピークv3がピークv1より高い

ピーク1とピーク4の位置の確認:

if(v4<v1-d1){ // ピークv4がピークv1より低い 

ピーク2とピーク3の間の最小距離を計算して確認します。

double d2=K2*(v3-v2); // ピークv3がピークv2より高い 
if(v4>v2+d2){ // ピークv4がピークv2より高い

点5が形成されているかどうかを確認します(Zジグザグは線1-3を上向きに破ります)。

if(v5>v5l){ // ジグザグが1-3を上向きに破る

右側の線1-3と線2-4が会うかどうかを確認するために高さ2-2 'と高さ4-4'を計算します。

double h4=v4x-v4; // 点4'が点4より高い
double h2=v2x-v2; // 点2'が点2より高い

波動と形は異なる色で描画されます。

// 他の色での描画
DrawObjects(SellColor,
            SellTargetColor,
            v1,
            v2,
            v3,
            v4,
            v5l,
            i1,
            i2,
            i3,
            i4,
            i5,
            time,
            i,
            tb,
            tv,
            rates_total);

波動動とターゲットの描画

すべての波動と形は単一のアルゴリズムを使用して描画されるため、1つのDrawObjects() 関数が使用されます。上下に向けられた要素は異なる色で描画されます。このために、色を泡ラスBuyColorパラメータまたはSellColorパラメータが関数に渡されます。ターゲットを定義する波動と形もさまざまな色で描画されるため、関数にはBuyTargetColorまたはSellTargetColorパラメータも渡されます。これらの変数は指標の外部変数であり、これを使用して希望の色を設定できます。色に加えて、いくつかの外部パラメータが必要です。波動とオブジェクトの描画関数に必要な追加パラメータのすべては以下のとおりです。

input bool   DrawWaves       =  true;             // 波動とオブジェクトの描画を可能にする
input color  BuyColor        =  clrAqua;          // 買いの波動の色
input color  SellColor       =  clrRed;           // 売りの波動の色
input int    WavesWidth      =  2;                // 波動の幅
input bool   DrawTarget      =  true;             // さらに形を有効/無効にする
input int    TargetWidth     =  1;                // オブジェクト幅
input color  BuyTargetColor  =  clrRoyalBlue;     // 買いオブジェクトの色
input color  SellTargetColor =  clrPaleVioletRed; // 売りオブジェクトの色

色を渡した後、ピークバーの値とインデックスを持つすべての変数が関数に渡されます。例外はピーク5の値で、ジグザグ線分の終わりでの値の代わりに線1-3の計算値が渡されます。ジグザグのすべての点の座標は棒グラフで与えられ、グラフィカルオブジェクトには時間が必要なので、'time'配列へのポインタが関数に渡されます。計算されたバーのインデックス(i)、ターゲットバー(tb)、ターゲット値(tv)、およびチャートのバーの合計数(rates_total)が関数に渡されます。 

本稿の冒頭では、波動やオブジェクトは、ジグザグが高/低(SrcSelectがSrc_HighLowに設定)または終値(SrcSelectがSrc_Closeに設定)を使用して計算された場合にのみ描画されるべきだと述べました。したがって、SrcSelect変数に応じてOnInit()関数(DrawWaves変数)で描画を強制的に無効にする必要があります。この目的のために、DrawWavesの代わりに使用される追加の変数を宣言します。

bool _DrawWaves;

次に、OnInit () 関数では、DrawWaves変数の値をそれに設定するまたはfalseに設定して無効にします。さらに、ターゲット描画バッファに不可視の色を設定します。

if(SrcSelect==Src_HighLow || SrcSelect==Src_Close){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
   PlotIndexSetInteger(2,PLOT_LINE_COLOR,clrNONE);
   PlotIndexSetInteger(3,PLOT_LINE_COLOR,clrNONE);      
}   

DrawObjects()関数に移りましょう。関数コード全体を表示した後でそれをより詳細に検討します。

void DrawObjects( color col,
                  color tcol,
                  double v1,
                  double v2,
                  double v3,
                  double v4,
                  double v5,
                  int i1,
                  int i2,
                  int i3,
                  int i4,
                  int i5,
                  const datetime & time[],
                  int i,
                  double target_bar,
                  double target_value,
                  int rates_total){

   // グラフィックオブジェクト名の接頭辞 
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

   // 波動動の描画                   
   fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
   fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
   fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
   fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

   // 形の描画
   if(DrawTarget){   
    
      datetime TargetTime;
      
      // ターゲットバーインデックスを整数を取得する 
      int tbc=(int)MathCeil(target_bar);
      
      if(tbc<rates_total){ // チャート上の既存のバー内のターゲット
         TargetTime=time[tbc];
      }
      else{ // 将来のターゲット
         TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
      }
      
      // ターゲットバーの線の値の計算
      double tv13=y3(i1,v1,i3,v3,tbc);   
      double tv24=y3(i2,v2,i4,v4,tbc);  
      double tv14=y3(i1,v1,i4,v4,tbc); 

      // 形

      fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);   
      fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
      fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);
      
      // 水平のターゲット線
      fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
      // 垂直のターゲット線 
      fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);      
   }
}

すべての描画はいくつかのトレンドラインを使用して行われ、最初に名前の共通接頭辞が形成されます。

// グラフィックオブジェクト名の接頭辞 
string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

次に波動が描画され、すべてのピークの座標が関数に渡されます。

// 波動動の描画                   
fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

目標を定義する形を描画し、描画が有効になっているか確認します。

// 形の描画
if(DrawTarget){  

有効になっている場合は、構造を描画します。指標を履歴に表示すると、ターゲットバーは既存の履歴バーに表示される可能性が最も高いですが、最近表示されたバーで波動が検出されると、ターゲットは最後のバーの右側に表示されます。よって、ターゲットバー時間の計算には2つのバリエーションが必要なので、この目的のために、変数を宣言します。

datetime TargetTime;

target_bar変数には少数値があるので、最も近い整数に繰り上げます。

// ターゲットバーインデックスを整数を取得する 
int tbc=(int)MathCeil(target_bar);

次に、得られたtbc変数を使用します。ここではMathFloor()関数を使用して、繰り下げた整数を取得します。形はインフォーマルな目的しか持たないため、これは最終的な結果に影響を与えません。MathCeil()を使用すると、線1-3と線2-4の端が必然的にターゲットバーの近くで交差し、形はより自然に見えます。

目標到達時間を決定しましょう。ターゲットが既存のバーのいずれかに配置されている場合、ターゲットバーのインデックスを計算し、その時間を '時間'配列から取得するだけで済みます。ターゲットが最後のバーの右側にある場合は、ターゲットが最後のバーから距離を置いたバーの数と時間を計算する必要があります。

if(tbc<rates_total){ // チャート上の既存のバー内のターゲット
   TargetTime=time[tbc];
}
else{ // 将来のターゲット
   TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
}

ターゲットバー上でのすべての線(1-3、2-4、1-4)の値を計算しましょう。

// ターゲットバーの線の値の計算
double tv13=y3(i1,v1,i3,v3,tbc);   
double tv24=y3(i2,v2,i4,v4,tbc);  
double tv14=y3(i1,v1,i4,v4,tbc); 

以前に計算された目標値が関数(target_value変数)に渡されるという事実にもかかわらず、それは形のためにも線2-4目についてさえも新たに計算されます。これは、target_bar変数の正確な値の代わりに、target_barよりも少し大きいtbc変数の値を使用するという事実と関連しています。これらの計算を使用して、正確なtarget_bar座標で、正確に線がtarget_valueレベルで交差するようにします。

計算された値を使って線を描きましょう。

fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);   
fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);

fObjTrend() 補助関数を使用して線を描画します。

void fObjTrend(   string  aObjName,
                  datetime aTime_1,
                  double   aPrice_1,
                  datetime aTime_2,
                  double   aPrice_2,
                  color    aColor      =  clrRed,  
                  color    aWidth      =  1,                
                  bool     aRay_1      =  false,
                  bool     aRay_2      =  false,
                  string   aText       =  "",
                  int      aWindow     =  0,                  
                  color    aStyle      =  0,
                  int      aChartID    =  0,
                  bool     aBack       =  false,
                  bool     aSelectable =  false,
                  bool     aSelected   =  false,
                  long     aTimeFrames =  OBJ_ALL_PERIODS
               ){
   ObjectCreate(aChartID,aObjName,OBJ_TREND,aWindow,aTime_1,aPrice_1,aTime_2,aPrice_2);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_BACK,aBack);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_COLOR,aColor);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTABLE,aSelectable);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTED,aSelected);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_TIMEFRAMES,aTimeFrames);
   ObjectSetString(aChartID,aObjName,OBJPROP_TEXT,aText);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_WIDTH,aWidth);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_STYLE,aStyle);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_LEFT,aRay_1);   
   ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_RIGHT,aRay_2);
   ObjectMove(aChartID,aObjName,0,aTime_1,aPrice_1);
   ObjectMove(aChartID,aObjName,1,aTime_2,aPrice_2);   
}

これは普遍的な関数で、トレンドラインを素早く作成してすべてのパラメータを設定するのにも使用できます。関数パラメータは表1に記載されています。表の先頭には最も頻繁に変更されるパラメータがあります(5つの必須パラメータ)。残りはオプションです。これらのパラメータを関数に渡すことはできません。このバリエーションは、関数の使用を非常に便利にします。

表1 fObjTrend()関数パラメータ

パラメータ 用途
string aObjName オブジェクト名
datetime aTime_1 最初のアンカーポイントの時間
double aPrice_1 最初のアンカーポイントの価格
datetime aTime_2 2番目のアンカーポイントの時間
double aPrice_2 2番目のアンカーポイントの価格
color aColor
color aWidth
bool aRay_1 最初のアンカーポイントから線を延長する
bool aRay_2 2番目のアンカーポイントから線を延長する
string aText ヒントテキスト
int aWindow サブウィンドウ
color aStyle 線のスタイル
int aChartID チャートID
bool aBack 背景に描画する
bool aSelectable オブジェクトが選択可能
bool aSelected オブジェクトが選択されている
long aTimeFrames 線を描画する時間枠

次に、ターゲットバーの垂直線とターゲットレベルの水平線の2つの線を追加する必要があります。

fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);  

その結果、波動と形のイメージが得られます。


図4 買い取引のターゲットを特定するWolfe波動

グラフィカルオブジェクトの削除

終値(SrcSelect=Src_Close)に基づくジグザグや別の指標に基づいたジグザグを使用する場合、バーの形成中に時々形が表示されたり消えたりすることがあります。この目的のために、矢印と点を持つバッファは、指標のメインループの開始時に消去されます。 

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;
      
UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;  

グラフィカルオブジェクトはループの開始時にも削除する必要があります。波動と形の描画が有効になっている場合、指標のループの始めにDeleteObjects() 関数が呼び出されます。

if(_DrawWaves){
   DeleteObjects(time[i]);
}  

下記はDeleteObjects()関数のコードです。

void DeleteObjects(datetime time){
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time)+"_";
   ObjectDelete(0,prefix+"12");
   ObjectDelete(0,prefix+"23");
   ObjectDelete(0,prefix+"34");
   ObjectDelete(0,prefix+"45");
   ObjectDelete(0,prefix+"13");
   ObjectDelete(0,prefix+"24"); 
   ObjectDelete(0,prefix+"14");    
   ObjectDelete(0,prefix+"67"); 
   ObjectDelete(0,prefix+"7h");    
}

計算されたバーの時間が関数に渡されます。計算されたバーに対応する名前を持つすべてのグラフィカルオブジェクトもこの関数で削除されます。

チャートから指標を削除する際には、指標によって作成されたすべてのグラフィカルオブジェクトを削除する必要があります。ObjectsDeleteAll()関数はDeInit()関数から呼び出されます。これは、指標操作が完了すると自動的に実行されます。指標名は、すべてのグラフィカルオブジェクトの接頭辞としても使用され、2番目のパラメータとして関数に渡されます。これによって確実に、指標に属するグラフィックオブジェクトのみが削除されます。

void OnDeinit(const int reason){
   ObjectsDeleteAll(0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw(0);
}  

アラート関数

新しい矢印を通知するアラート関数を指標に追加しましょう。この関数は、「グラフィカルインターフェイスを使用したユニバーサルトレンド」で説明されている、同指標で使用されている関数に似ています。 

アラート関数では、形成中のバー(高安に基づいたジグザグに適す)またはすでに成形されたバー(終値または他の指標に基づいたジグザグに適す)の矢印の出現を追跡できます。アラートの種類を選択するための列挙を作成しましょう。

enum EAlerts{
   Alerts_off=0,  // アラートが無効
   Alerts_Bar0=1, // 形成中のバーのアラート
   Alerts_Bar1=2  // 完成したバーのアラート
};

変数をプロパティウィンドウに追加します。

input EAlerts              Alerts         =  Alerts_off;

アラート関数のコードは、個別のCheckAlerts()関数として使用できます。この関数にはチャート上のバーの数と時間配列が渡されます。

void CheckAlerts(int rates_total,const datetime & time[]){
   if(Alerts!=Alerts_off){ // アラートが有効
      static datetime tm0=0; // 前回の買いアラートのバーの時間を表す変数
      static datetime tm1=0; // 前回の買いアラートのバーの時間を表す変数
      if(tm0==0){ // 初めての関数の実行
         // 変数の初期化
         tm0=time[rates_total-1];
         tm1=time[rates_total-1];
      }
      string mes=""; // メッセージを表す変数

      // 上向き矢印があり、最後のバーにアラートがなかった
      if(UpArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm0!=time[rates_total-1]
      ){
         tm0=time[rates_total-1]; // 最後のアラートの時間を記憶する
         mes=mes+" buy"; // メッセージを形成
      }

      // 下向き矢印があり、最後のバーにアラートがなかった
      if(DnArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm1!=time[rates_total-1]
      ){
         tm1=time[rates_total-1]; // 最後のアラートの時間を記憶する
         mes=mes+" sell"; // メッセージを形成
      } 
      if(mes!=""){ // メッセージがある
         // メッセージのあるウィンドウを開く
         Alert(MQLInfoString(MQL_PROGRAM_NAME)+"("+Symbol()+","+IntegerToString(PeriodSeconds()/60)+"):"+mes);
      }        
   }   
}

CheckAlerts()関数はループの後でOnCalculate()関数の終わりで呼び出されます。OnCalculate()関数の最後には、波動と形の描画を高速化するためのチャートリフレッシュ関数も呼び出されます。

if(_DrawWaves){
   ChartRedraw(0);
}

指標の作成はこれで完了です。指標はiWolfeWavesと呼ばれ、本稿への添付ファイルでも利用可能です。

エキスパートアドバイザー

ここで作成したのはかなり複雑な指標です。静的な履歴での作業を確認するだけでなく、考慮されている視覚的分析方法の有効性を評価してみましょう。これを行うために、簡単なエキスパートアドバイザーを作成します。

EAは、指標のすべてのシグナルで注文を出す必要があります。これによって、効果の評価が可能になります。したがって、ヘッジ勘定で機能し、未決済ポジションの数に制限はないものとします。

エディタで新しいエキスパートアドバイザーを作成してeWolfeWavesと名付けます。指標から外部パラメータをコピーして、EAファイルに追加します。以下に、ストップロスとテイクプロフィットを特定するための追加のパラメータを追加します。

input double               StopLoss_K     =  1;      // ストップロス比率
input bool                 FixedSLTP      =  false;  // 固定されたストップロスとテイクプロフィット
input int                  StopLoss       =  50;     // 固定されたストップロス値
input int                  TakeProfit     =  50;     // 固定されたテイクプロフィット値

これらのパラメータによって、2つのSLオプションとTPオプションのいずれかを選択できるようになります。

FixedSLTP=falseの場合はStopLoss_K変数が使われます。この場合、ターゲットを示す点のレベルでの指標値に基づいてテイクプロフィット値を設定し、ストップロスはStopLoss_K乗数を使用してテイクプロフィットに比例して計算します。このSLとTPを定義するオプションは、価格に基づいた(SrcSelectがSrc_HighLowまたはSrc_Closeに設定)ジグザグにのみ適しています。

FixedSLTP=trueの場合はStopLoss変数とTakeProfit変数が使われます。これは、指標に基づくジグザグと価格に基づくジグザグの両方で使用できます。   

OnInit()関数で口座の種類を確認し、口座がヘッジを許可しない場合にはEAの操作を停止します。

if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
   Print("Its not hedging account");
   return(INIT_FAILED);
}

エキスパートアドバイザーがビジュアルモードでテストされていない場合、波動と形の描画は無効になります。

bool _DrawWaves;

if(MQLInfoInteger(MQL_VISUAL_MODE)){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
}

iWolfeWaves指標がiCustom()関数によって呼び出されたと、DrawWavesの代わりに_DrawWaves変数が使用されます。指標を呼び出して正常に読み込まれたかどうかを確認します。

h=iCustom(  Symbol(),
            Period(),
            "iWolfeWaves",
            Alerts,
            SrcSelect,
            DirSelect,
            RSIPeriod,
            RSIPrice,
            MAPeriod,
            MAShift,
            MAMethod,
            MAPrice,
            CCIPeriod,
            CCIPrice,
            ZZPeriod,
            K1,
            K2,
            K3,
            _DrawWaves,
            BuyColor,
            SellColor,
            WavesWidth,
            DrawTarget,
            TargetWidth,
            BuyTargetColor,
            SellTargetColor);
            
if(h==INVALID_HANDLE){
   Print("Cant load indicator");
   return(INIT_FAILED);
}

指標の読み込みが失敗した場合はEAの操作は停止するべきです。

高安値に基づいた指標を使用する場合、矢印は消えないので、EAは不完全な現在のバーで動作します。それ以外の場合は、最初の完全なバーの指標の矢印を確認する必要があるので、この目的のために、EAのグローバル変数 'Shift'を使用します。

int Shift;

ジグザグの種類に応じて必要な値を設定します。 

if(SrcSelect==Src_HighLow){
   Shift=0;
}
else{
   Shift=1;
}

以下はOnInit()関数のコード全体です。

int OnInit(){

   // 口座の種類の確認
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
      Print("Its not hedging account");
      return(INIT_FAILED);
   }

   // 波動と形の描画を無効にする

   bool _DrawWaves;
   
   if(MQLInfoInteger(MQL_VISUAL_MODE)){
      _DrawWaves=DrawWaves;
   }
   else{
      _DrawWaves=false;
   }

   // 指標の読み込み
   h=iCustom(  Symbol(),
               Period(),
               "iWolfeWaves",
               Alerts,
               SrcSelect,
               DirSelect,
               RSIPeriod,
               RSIPrice,
               MAPeriod,
               MAShift,
               MAMethod,
               MAPrice,
               CCIPeriod,
               CCIPrice,
               ZZPeriod,
               K1,
               K2,
               K3,
               _DrawWaves,
               BuyColor,
               SellColor,
               WavesWidth,
               DrawTarget,
               TargetWidth,
               BuyTargetColor,
               SellTargetColor);
       
   // 指標が正常に読み込まれたかどうかを確認する         
   if(h==INVALID_HANDLE){
      Print("Cant load indicator");
      return(INIT_FAILED);
   }
   
   // EAが指標矢印を確認すべきバーを定義する
   if(SrcSelect==Src_HighLow){
      Shift=0;
   }
   else{
      Shift=1;
   }

   return(INIT_SUCCEEDED);
}

OnTick()関数に移りましょう。EAはバーとティックの両方で動作する必要があります。形成中のバーと最後に処理されたバーの時間を表す変数を追加します(変数はOnTick() 関数で宣言されます)。

datetime tm[1];     // 形成中のバーの時間
static datetime lt; // 最後に処理されたバーの時間

最後の(形成中の)バーの時間を取得します。

if(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;

時間を確認します。

if(Shift==0 || tm[0]!=lt){

Shift==0の場合やlt変数が形成中のバーの時間と等しくない場合には、EAはすべてのティックで動作します。 

補助変数を宣言して指標値を取得します。

double tp,sl; // 決済逆指値と決済指値を表す変数

double buf_buy[1];         // 買い矢印
double buf_sell[1];        // 売り矢印

double buf_buy_target[1];  // 買いターゲット
double buf_sell_target[1]; // 売りターゲット

if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

取引シグナルがある場合、決済逆指値や決済指値が計算され、ポジションが開かれます。

// 矢印があり、このバーに買いポジションは開かれていない
if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
   // 決済逆指値と決済指値
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_buy_target[0],_Digits);
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
   }
   // 開く
   if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
   // 最後に開いた時間を「記憶」する
   LastBuyTime=tm[0];
}

固定SLと固定TPが使用されている場合(FixedStopLoss=true)、TakeProfit変数の値に_Point を乗じた値をポジションの始値(買った際の売り呼び値)に加算して、決済指値を計算します。 決済逆指値を計算するには、StopLoss変数に_Pointを乗じた値を始値から差し引きます。計算後は、NormalizeDouble()関数を使用して銘柄のクオーツの桁数に対応する小数点以下の桁(この数値は_Digits 変数で取得)まで 結果値を正規化します。

SLとTPが固定されていない場合は、決済指値を決定してから決済逆指値を計算します。ポジションが正常に開かれないと、 OnTick() 関数が終了し、次のティックでポジションを開く新しい試みが行われます。この試みは、指標シグナルが存在する限り、すなわち1つのバーにわたって、常に行われます。ポジションが正常に開かれた場合は、現在のバーの時間がLastBuyTime変数に割り当てられ、同じバーで再開しないようにします(ティックを使用する、つまりShift=0の場合)。LastBuyTimeはエキスパートアドバイザーのグローバル変数です。

売りは買いと似ていますが、いくつかの修正点があります。

// 矢印があり、このバーには売りポジションが開かれていない
if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
   // 決済逆指値と決済指値
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_sell_target[0],_Digits);
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
   }
   // 開く
   if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
   // 最後に開いた時間を「記憶」する
   LastSellTime=tm[0];        
}  

買い取引の場合、LastBuyTime変数の代わりにLastSellTimeが使用され、決済逆指値や決済指値は買い呼び値を使用して計算されます。

最後に、形成中のバーの時間がlt変数に割り当てられ、EAが同じバーで何のアクションも実行しないようにします(EAが各バーで動作するように設定されている、つまりShift=1の場合)。以下はOnTick()関数のコード全体です。

void OnTick(){
   
   datetime tm[1]; // 形成中のバーの時間
   static datetime lt; // 最後に処理されたバーの時間
   
   // 時間をコピーする
   if(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;
   
   if(Shift==0 || tm[0]!=lt){ // EAがバーごとに作動するかを確認する

      double tp,sl; // 決済逆指値と決済指値を表す変数

      double buf_buy[1];         // 買い矢印
      double buf_sell[1];        // 売り矢印
      
      double buf_buy_target[1];  // 買いターゲット
      double buf_sell_target[1]; // 売りターゲット
      
      if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
      if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
      if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
      if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

      // 矢印があり、このバーに買いポジションは開かれていない
      if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
         // 決済逆指値と決済指値
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_buy_target[0],_Digits);
            double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
         }
         // 開く
         if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
         // 最後に開いた時間を「記憶」する
         LastBuyTime=tm[0];
      }
      
      // 矢印があり、このバーには売りポジションが開かれていない
      if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
         // 決済逆指値と決済指値
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_sell_target[0],_Digits);
            double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
         }
         // 開く
         if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
         // 最後に開いた時間を「記憶」する
         LastSellTime=tm[0];        
      }      
      
      lt=tm[0];
   }
}

エキスパートアドバイザーコードは添付されたeWolfeWavesファイルにあります。 

結果として得られるエキスパートアドバイザーをテストしましょう。テスト後に指標をチャートに取り付けると、ポジションがあるかどうかに関係なく、EAが各矢印で市場に参入していることがわかります(図5)。


図5 エキスパートアドバイザーがそれぞれの矢印で参入する

もちろん、一番関心深いのは、指標がどの程度効果的であるかです。図6は、EURUSD H1全体の履歴を使用したデフォルト設定によるEAテストの結果を示します。


図6 EURUSD H1全体の履歴に関するEAテスト結果

テスト期間の冒頭に著しい下降が見られますが、これは初期段階での履歴の質の低さに関係する可能性があります。さらに、1991年頃から着実に成長が始まります。一般に言って、テスト結果は、最適化と追加のチェックがなくても肯定的です。

Bill Wolfe氏の本からのいくつかのヒント

Bill Wolfe氏は波動の検出ルールに加えていくつかのヒントを提供しており、それらを「心理的および技術的な注釈」と呼んでいます。最も重要な技術的注釈の1つは、ティックボリュームの監視についての助言で、ティックボリュームは反転ポイントで減少することがあってこの減少は反転を示す可能性があるというものです。2番目のアドバイスはトレンドラインに従うことです。Bill Wolfe氏によって発見された波動はしばしば、トレンドブレークの後、すなわちトレンドラインのブレークアウト後に発生し、トレンドブレークアウト後に現れる波がより信頼できます。3番目のアドバイスは、線1-4、特に点4を監視し、後方波、ボリュームの大幅な増加、または迅速な利益のように、予期しないイベントが発生した場合にエグジットすることです。

終わりに

エキスパートアドバイザーのテストの肯定的な結果(デフォルト設定であっても)は、本稿で説明した視覚的分析法が確実に効果的であることを示唆し、さらなる調査は興味深いかもしれません。

読者の中には、指標を改善したい方もいらっしゃるでしょう。現時点では、指標の外部パラメータには3つの可変係数K1、K2、K3があります。K1は、点1に対する点3の位置と点1に対する点4の位置を検査するために使用されます。おそらく、これらの検査のためには別々の係数を使用するほうがよいでしょう。他方、パラメータの数の増加は、最適化を複雑にし、最適化の代わりにリスクの過適合を増加させるので、係数K1およびK2を組み合わせる方が良いかもしれません。これによって、指標の設定が簡単になり、分かりやすくなります。一方、係数を1つにするほうが良いかもしれません。指標コードは、変更を容易にするために関数によって明確に分けられています。指標は、誰によってでもまちまちな方法で修正できます。  

指標は、Wolfe波動の検索に加えて、他のジグザグパターンを検索する他の指標を作成するときのテンプレートとして使用できます。これには、CheckUpとCheckDn関数のコードを変更するだけです。最も重要なことは、ジグザグ値へのアクセスの問題が解決されていることです。

変数CurCount、PreCount、LastTimeを使ったトリックに特別な注意を払いたいと思います。これは、本稿で分析した狭い問題に対する解決策ではありません。指標を開発する際には、中間計算で得られた補助値用の追加バッファが必要な場合がよくあります。各バーでは、前のバッファ要素からの値が現在のバッファ要素に移動され、この値が変更されることがあります。1つの要素の値は計算で使用され、バッファ全体がこの目的に使用されます。2つの変数を使用すると、指標が使用するRAMの量を大幅に減らすことができます。

添付ファイル

本稿で作成した指標ファイルとエキスパートアドバイザーは、以下に添付されています。ファイルは適切なフォルダに配置されているので、端末でも同じフォルダに保存する必要があります。添付ファイルは下記です。


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

添付されたファイル |
MQL5.zip (8.67 KB)
グラフィカルインターフェイスX:マルチラインテキストボックスでのテキスト選択(ビルド13) グラフィカルインターフェイスX:マルチラインテキストボックスでのテキスト選択(ビルド13)
本稿では、他のテキストエディタと同様に、さまざまなキーの組み合わせによってテキストを選択して選択したテキストを削除する機能を実装します。さらに、引き続きコードを最適化し、ライブラリの進化の第2段階の最終プロセスではすべてのコントロールが別々の画像(キャンバス)としてレンダリングされるため、これに向かってクラスを準備します。
人工知能を用いたTDシーケンシャル(トーマス デマークのシーケンシャル) 人工知能を用いたTDシーケンシャル(トーマス デマークのシーケンシャル)
本稿では、よく知られている戦略とニューラルネットワークを融合させた成功裡の取引方法を説明します。これは、人工知能システムを用いたトーマス デマークのシーケンシャル戦略に関するもので、「セットアップ」シグナルと「インターセクション」シグナルを使用して、戦略の最初の部分のみが適用されます。
一个绘制支撑和阻力线的指标实例 一个绘制支撑和阻力线的指标实例
本文提供了一个例子,它是关于如何实现根据指定条件绘制支撑和阻力线的指标的,另外,它也包含了一个可以使用的指标,您将看到,创建指标的过程有多么简单。您也将能学习如何通过修改指标代码来构建用于绘制任何所需线形的条件。
銘柄とEAのORDER_MAGICによるバランス/エクイティチャートの分析 銘柄とEAのORDER_MAGICによるバランス/エクイティチャートの分析
MetaTrader 5のヘッジ導入は、複数のエキスパートアドバイザーを1つの取引口座で同時に取引する絶好の機会を提供します。1つの戦略が利益を上げ、2番目の戦略が損失を生み出している場合、利益チャートはゼロに近い値を表示するかもしれません。この場合、各取引戦略のバランスチャートとエクイティチャートを別々に作成することが便利です。