フラグパターン

Dmitry Fedoseev | 25 9月, 2017


目次

はじめに

フラグパターンの基本的な特徴は、その名前にも関係していますが、顕著な垂直価格変動( "Flagpole")とそれに続いてフラグを形成するゆったりと延びる水平方向の動きです(図1)。


図1 フラグ

テクニカル分析関連書籍やウェブサイトでは、フラグパターンは、しばしばペナントパターンと平行して考慮されます。フラグとは異なり、ペナントは三角形です(図2)。そのため、いくつかのテクニカル分析関連リソースでは、フラグパターンは三角形パターンとともに分析されます。


図2 ペナント

ペナントと三角形は、同じパターンの異なる名前にすぎないようです。これらの2つのパターンは、Tomas Bulkowskiによるチャートパターンの百科事典などのテクニカル分析の書籍では別々に言及されています。また、この本にはウェッジパターンの説明があります。これは水平にプロットされた同様の三角形で、狭い部分は左側にあり、右側では価格はより広い範囲で動いていきます(図3)。


図3 ウェッジ

ウェッジパターンに加えて、膨張三角形だけでなく、フラグに似たさまざまな長方形パターンがあります。したがって、ペナントと三角形、ウェッジと膨張三角形、及びフラグと水平パターンを区別するための明確な規則が必要がです。この質問は本稿の初めに検討されます。次に、これらすべてのパターンを検索するための指標を作成します。 

ペナントと三角形の違い

ペナントと三角形の違いだけでなく、上記の形の似たパターン間の違いを考えてみましょう。

  • 長方形パターンとフラグ
  • 三角形とペナント
  • 膨張三角形とウェッジ

長方形パターン、三角形、膨張三角形は1つのカテゴリに分類され、フラグ、ペナント、ウェッジはあと1つのカテゴリに分類されます。

初めのカテゴリでは、パターンは価格反転ポイントから形成される(図4)ため、ジグザグ指標を使用して検索することができます。


図4 パターン:a - 水平パターン、b - 三角形、c - 膨張三角形 
示されているのは予測される上向きの動き(買いのため)のパターン

2番目のカテゴリのパターンは、その領域を埋めるバーで構成されています(図5)。もちろん、図に示されているような明示的な形はチャートではほとんど見ることができませんが、大事なのは隣接しているバーが大きく重なりあってパターンを形成しているということです。 


図5 パターン:a - フラグ、b - ペナント、c - ウェッジ
示されているのは予測される上向きの動き(買いのため)のパターン

カテゴリとその基本的な違いを特定したので、個々のパターンを別々に考察してみましょう。 


水平パターン

最初のカテゴリのパターンを考えてみましょう。このようなパターンの特定に便利なのはジグザグ指標の使用です。このカテゴリの最初のパターンは「水平パターン」で、これは第2カテゴリの「フラグ」に対応します。しかし、ほとんどのテクニカル分析関連リソースでは、それは「フラグ」と呼ばれる「水平パターン」です。

水平パターンを形成するためには、価格は顕著な垂直方向の動きをし、ほぼ同じ価格レベルで少なくとも2つの頂部、そしてほぼ同じ価格レベルで2つの底部を形成する必要があります。価格は各3つまたはそれ以上の頂部と底部を形成するかもしれません(図6)。したがって、指標パラメータの1つが、パターンを形成する頂部と底部の数を特定します。


図6 水平パターン:a - 2つの頂部/底部から形成されたもの、b - 3つの頂部/底部から形成されたもの
示されているのは予測される上向きの動き(買いのため)のパターン 

パターンの上端と下端は水平である必要はありませんが、平行ではなければならないので、指標にはパターンの勾配(水平、上向き、下向き)を選択するためのもう1つのパラメータがあります(図7)。


図7 a - 水平パターン、b - 勾配が下向きのパターン、c - 勾配が上向きのパターン 
示されているのは予測される上向きの動き(買いのため)のパターン

もちろん、上向きまたは下向きの勾配を持つパターンは水平とは呼べませんが、原則として水平パターンに非常に近いパターンです。

価格が頂部によって形成されたレベルを超えるとパターン形成が完了します(図8)。

 
図8 パターン形成の終了と買いポジションの始まり:
a — 水平パターン、
b — 勾配が下向きのパターン 

勾配が上向きのパターンでは、エントリーのタイミングは、頂部の勾配の代わりに最後の頂部の水平レベルを考慮して特定されます(図9)。


図9 上向きの傾斜パターンのエントリー(買い)の特定

水平なレベルを持つ単純なバリアントは勾配が下向きのパターンにも使用できるため、指標には、パターンタイプに関係なくレベルのタイプを選択するための変数があります。

「水平パターン」の目標は、パターンが形成される前の垂直移動の大きさによって決まります。価格形成後に価格が動く距離は、その形成前の距離と同じでなければなりません(図10)。


図10 目標の決定。価格がパターンに入る前に動いた距離L1は
パターンを出た後の距離L2に等しい 

パターンの上端と下端が平行であるため、目標を決定するためにはより単純なバリアントを使用することができます。価格の最初の頂点までの距離を測定し、最後の底部から同じだけ上に動きます(図11)。


図11 目標を決定する簡単な方法。最初の頂点の形成前に価格が動いた距離L1は
最後の底部から目標までの距離L2に等しい


収縮三角形

「収縮三角形」パターンは「水平パターン」とは少し異なりますが、唯一の違いは、パターンを形成するジグザグセグメントが連続して小さくならなければならないことです(図12)。


図12 収縮三角形のセグメント3-4は1-2より短く、
セグメント5-6は3-4より短いことが必要
 

残りの条件は三角形の水平位置または上/下の勾配、最後の2つの頂部または最後の頂部で描画された水平レベルによって形成される抵抗レベルのブレイクアウトにおけるエントリ、目標レベルの計算において水平パターンと同様です。


膨張三角形

上記の収縮三角形に関する理想は膨張三角形に適用することができますが、唯一の違いは、パターンを形成するジグザグセグメントが大きくなることです(図13)。


図13 膨張三角形のセグメント3-4は1-2より長く、
セグメント5-6は3-4より長いことが必要
 

これらの3パターンはこのようにかなり似ているので、検索のためには単一の汎用指標を作成できます。  


水平パターンと三角形を検索するための汎用指標

指標の作成には不変なジグザグ稿にあるiUniZigZagSW指標を使用します。CSorceData.mqh、CZZDirection.mqh及びCZZDraw.mqhの追加ファイルも必要です。これらのファイル及びiUniZigZagSW.mq5ファイルは「不変なジグザグ」稿の添付ファイル からダウンロードできます。アーカイブをダウンロードして解凍し、そこからMQL5フォルダをターミナルデータフォルダにコピーします。コピー後は、iUniZigZagSW.mq5などの複数ファイルを含んだZigZagフォルダがMQL5/Indicatorsに生成され、MQL5/IncludesにはCSorceData.mqh、CZZDirection.mqh及びCZZDraw.mqhを含むZigZagフォルダが生成されます。ファイルをコピーしたら、ターミナルを再起動してまたはMetaEditorで指標をコンパイルします。iUniZigZagSW指標がターミナルのチャート上で実行できることを確認してください。

WOLFE波動稿ではiWolfeWaves_Step_1.mq5ファイルが指標作成の中間段階で保存されました。iUniZigZagSW.mq5指標のアクセスがiCustom()関数を使って実装されており、また、ジグザグのすべての上端と下端を持つ配列が中に形成されています。「WOLFE波動」稿の添付ファイルをダウンロードして解凍しiWolfeWaves_Step_1.mq5をMQL5/Indicatorsにコピーして、名前を"iHorizontalFormation"に変えてMetaEditorで開きます。このファイルでは、水平パターン検出指標を使用したすべての作業が実行されます。iUniZigZagSW指標へのパスをこのファイルて変更する必要があるかもしれないので、確認のために指標をコンパイルしてチャート上で実行してみます。"Error load indicator" (指標読み込みエラー)メッセージが表示されたウィンドウが開いたらOnInit()でiCustom()の呼び出しを探して指標銘を"iUniZigZagSW"から"ZigZags\\iUniZigZagSW"に変更します。修正後、再度指標をコンパイルして、エラーなしでチャート上で実行できることを確認します。この段階では、指標は何も描画しません。

ここで説明するパターン検索プロセス全体は、いくつかの独立したタスクに分けられます。

  1. パターンの形成に先立つ価格変動価の特定
  2. パターンの形の特定
  3. パターンの勾配の特定
  4. パターン形成の完了:パターン形成直後またはブレイクアウトレベルの待機中
  5. 目標の決定

各タスク(最初のタスクを除く)にはいくつかのソリューションオプションが用意されていて、3つのパターンすべてを識別するために使用できる汎用指標を作成するのに役立ちます。指標パラメータウィンドウのオプションは列挙体のドロップダウンリストを使用して切り替えることができます。 

以下は形を選択するための列挙体(パターンタイプ)です。

enum EPatternType{
   PatternTapered,
   PatternRectangular,
   PatternExpanding
};

プロパティウィンドウの対応する変数はこちらです。

input EPatternType         Pattern        =  PatternRectangular;

このパラメータを使用すると、パターンの形(PatternTapered - 縮小三角形、PatternRectangular - 長方形、PatternExpanding - 膨張三角形)を選択できます。

以下はパターンの勾配を選択するための列挙体です。

enum EInclineType{
   InclineAlong,
   InclineHorizontally,
   InclineAgainst
};

プロパティウィンドウの対応する変数はこちらです。

input EInclineType         Incline        =  InclineHorizontally; 

このパラメータを使用すると、パターンの傾斜(InclineAlong - 予想される動きの方向への傾斜(買いは上向き、売りは下向き)、InclineHorizontally - 傾斜なし、InclineAgainst - 予想された動きの反対方向への傾斜)を選択できます。

以下はパターン補完方法を選択するための列挙体です。

enum EEndType{
   Immediately,
   OneLastVertex,
   TwoLastVertices
};

プロパティウィンドウの対応する変数はこちらです。

input EEndType             CompletionType =  Immediately;

パターンの作成直後、OneLastVertex — パターンの最後の一番上の最後の水平レベルのブレイクアウト後、TwoLastVertices — パターンの最後の2つの頂点によって形成された最後のレベルのブレイクアウトの後 のオプションが利用できます。

以下は目的計算オプションを選択する列挙体です。

enum ETargetType{
   FromVertexToVertex,
   OneVertex,
   TwoVertices
};

プロパティウィンドウの対応する変数はこちらです。

input ETargetType          Target         =  OneVertex;

VertexToVertex(図11)、OneVertex(図10)、TwoVertices(パターンの2つの初期底部が使用、図14)のオプションが利用できます。


図14 3頂部パターン。目標決定オプションはTwoVertices、
パターン補完方法はOneLastVertex

パターン補完メソッドがImmediatelyに設定されている場合はFromVertexToVertexのみが使用できるため、Targetパラメーターは無効です。他の2つのパターン補完オプション(OneLastVertexとTwoLastVertices)では、3つのCompletionTypeオプションすべてで異なる組み合わせが可能です。目標決定にOneVertexまたはTwoVerticesオプションが選択された場合、目標を決定するには最初の1つまたは2つの底部(図14の点2または点2及び4)が使用され、ブレイクアウトレベルは、最後の1つまたは2つの頂部(図14の点5または点3及び5)に基づいて決定されるという特徴にはご注意ください。2頂部パターンが使用された場合、点3または点1及び3が使用されます。

タスク1を解決するには、パターンに先行する価格移動のサイズを決定する1つのパラメータが必要です。

input double               K1             =  1.5;

セグメント1-2(図14)の高さはパターンの基準(基準サイズ)とみなされ、すべてのサイズの確認はそれに対して実行されます。K1パラメータは、セグメント0-1がセグメント1-2より何倍高くなければならないかを特定します。

パターン形状を決定するために、K2パラメータを使用します。

input double               K2             =  0.25;

パラメータの値が小さいほど、パターンの高さは全長にわたって一定でなければなりません。三角形パターン(拡大及び縮小)では、パラメータの増加は、最も明確に表現された三角形のパターンを検索することを意味します。

パターンの勾配を特定するには、K3パラメータを使用します。

input double               K3             =  0.25;

小さなパラメータ値は、指標が水平に配置されたパターンを検索することを意味します。傾斜パターンを検索する場合は、K2パラメータの値が大きいほど、勾配が明確なパターンを見つけることができます。

最後に、下記は主要パラメータの1つです。

input int                  N              =  2;

Nパラメータは、パターン頂点の数を決定します。 

その結果、(ZigZagパラメータに加えて)次の外部パラメータのセットが存在します。

input EPatternType         Pattern        =  PatternRectangular;
input EInclineType         Incline        =  InclineHorizontally;     
input double               K1             =  1.5;
input double               K2             =  0.25;
input double               K3             =  0.25;
input int                  N              =  2;
input EEndType             CompletionType =  Immediately;
input ETargetType          Target         =  OneVertex;

Nパラメータを使用して、パターンを特定するために必要なジグザグ点の数を計算します。まず、グローバル変数を宣言します。

int RequiredCount;

OnInit()関数でその値を計算します。

RequiredCount=N*2+2;

2*Nはパターンを形成する頂点の数(N個の頂部とN個の底部)です。別の頂点が以前の価格の動きを特定し、また別の頂点が新しいジグザグセグメントの最後の点です(計算には使用されません)。

それ以降の操作はすべてOnTick()関数で実行されます。新しいコードは、指標のメインループの最後に追加されます。十分なジグザグ点があって方向が変わった場合にのみ、パターン形成条件が確認されます。価格とレベルはジグザグの変更ごとに確認されます。

if(CurCount>=RequiredCount){
   if(CurDir!=PreDir){      
      // 条件の確認

   }
   // 価格とレベルの確認

} 

まず、基本値、すなわちセグメント1-2の高さを計算します(図14)。この値は、すべてのパターン形成条件を確認するときに使用されます。次に、タスク1の状態、すなわち1つ前の動きの大きさが確認されます。

int li=CurCount-RequiredCount;                                            // PeackTrough配列内の初期パターンポイントのインデックス
double base=MathAbs(PeackTrough[li+1].Val-PeackTrough[li+2].Val);        // 基本値
double l1=MathAbs(PeackTrough[li+1].Val-PeackTrough[li].Val);             // セグメント1-2の高さ
   if(l1>=base*K1){                                                       // 1つ前の動きのサイズの確認
        // その他の確認

   }

さらなる確認は、ジグザグの最後のセグメントが上向きか下向きかによって異なります。

if(CurDir==1){              // 最後のジグザグセグメントが上向き
   // 上向きの場合の条件確認  
             
}
else if(CurDir==-1){        // 最後のジグザグセグメントが下向き
   // 下向きの場合の条件確認

}

上向きの場合の条件確認について考えてみましょう。

if(CheckForm(li,base) && CheckInclineForBuy(li,base)){      // 形と方向の確認
   if(CompletionType==Immediately){ 
      // 指標矢印を描画する
      UpArrowBuffer[i]=low[i];
      // 目標点を描画する
      UpDotBuffer[i]=PeackTrough[CurCount-1].Val+l1;
   }
   else{
      // ブレイクアウトレベルパラメータの設定
      SetLevelParameters(1);
      // 目標パラメータの設定
      SetTarget(1,li);
   }
} 

パターン形成条件は、パターンフォームを確認するCheckForm()と勾配を確認するCheckInclineForBuy()の2つの関数を使用して確認されます。形と勾配が条件を満たす場合は、チャート上に矢印と目標点のいずれかが描画されます。パターン補完のタイプに応じて、さらに監視されるブレイクアウトレベルのパラメータが設定されます。

CheckForm()関数。PeackTrough配列のパターンの最初の点のインデックスと基本値 'base'が関数に渡されます。コードは次のとおりです。

bool CheckForm(int li,double base){               
   switch(Pattern){
      case PatternTapered: 
         // 収縮
         return(CheckFormTapered(li,base));
      break;               
      case PatternRectangular: 
         // 長方形
         return(CheckFormRectangular(li,base));
      break;
      case PatternExpanding: 
         // 膨張
         return(CheckFormExpanding(li,base));
      break;
   }
   return(true);
}

この関数では、Patternパラメータの値に応じて適切な関数(収縮三角形ではCheckFormTapered()、 長方形パターンではCheckFormRectangular()、膨張三角形ではCheckFormExpanding())が呼び出されます。

以下がCheckFormTapered()関数です。

bool CheckFormTapered(int li,double base){
   // 1からループする。初めのセグメントは確認されず
   // 続くすべてのセグメントはそれに対して確認される 
   for(int i=1;i<N;i++){ 
      // パターンの次の頂部のインデックスの計算 
      int j=li+1+i*2;
      // 次のセグメントの値 
      double lv=MathAbs(PeackTrough[j].Val-PeackTrough[j+1].Val);
      // 前のセグメントの値
      double lp=MathAbs(PeackTrough[j-2].Val-PeackTrough[j-1].Val);
      // 前のセグメントのほうが大きいはずである
      // そうでなければ関数はfalseを返す   
      if(!(lp-lv>K2*base)){
         return(false);
      }
   } 
   return(true);
}

この関数では、パターンを形成するジグザグセグメントがループで確認され、後続の各セグメントは前のセグメントよりも小さくなければなりません。

CheckFormExpanding()関数も同様ですが、違いが1つあります。

if(!(lv-lp>K2*base)){
   return(false);
}

この条件を満たすために、後続の各セグメントは前のセグメントよりも大きくなければなりません。

以下がCheckFormRectangular()関数です。

bool CheckFormRectangular(int li,double base){   
   // 最初のものを除くすべての頂部をループする      
   for(int i=1;i<N;i++){
      // 次の頂部のインデックスの計算 
      int j=li+1+i*2; 
      // 次のセグメントのサイズの計算
      double lv=MathAbs(PeackTrough[j].Val-PeackTrough[j+1].Val);
      // セグメントは基準値と大きく異なるべきではない 
      if(MathAbs(lv-base)>K2*base){
         return(false); 
      }
   }
   return(true);
}

この関数では、各セグメントが基準値と比較されます。差が大きい場合、関数はfalseを返します。

形が条件を満たすと、勾配が確認されます。以下がCheckInclineForBuy()関数です。

bool CheckInclineForBuy(int li,double base){                 
   switch(Incline){
      case InclineAlong:
         // 動きの方向への傾斜
         return(CheckInclineUp(li,base));
      break;
      case InclineHorizontally:
         // 傾斜なし
         return(CheckInclineHorizontally(li,base));
      break;                     
      case InclineAgainst:
         // 動きの方向に反する傾斜
         return(CheckInclineDn(li,base));
      break;
   } 
   return(true);
}  


売りのための勾配確認関数は2行で異なります。

bool CheckInclineForSell(int li,double base){                 
   switch(Incline){
      case InclineAlong:
         // 動きの方向への傾斜
         return(CheckInclineDn(li,base));
      break;
      case InclineHorizontally:
         // 傾斜なし
         return(CheckInclineHorizontally(li,base));
      break;                     
      case InclineAgainst:
         // 動きの方向に反する傾斜
         return(CheckInclineUp(li,base));
      break;
   } 
   return(true);
} 

InclineがInclineAlong(動きの方向)に等しい場合、買いのためにはCheckInclineUp() 、売りのためにはCheckInclineDn() が呼び出されます。Incline = InclineAgainstでは反対です。

以下はパターンの上向き傾斜をチェックするCheckInclineUp()関数です。

bool CheckInclineUp(int li,double base){   
   // 最初のものを除くすべての頂部をループする      
   for(int v=1;v<N;v++){
      // 次の頂部のインデックスの計算
      int vi=li+1+v*2;
      // 次のジグザグセグメントの中央の計算
      double mc=(PeackTrough[vi].Val+PeackTrough[vi+1].Val)/2;
      // 前のジグザグセグメントの中央の計算
      double mp=(PeackTrough[vi-2].Val+PeackTrough[vi-1].Val)/2;
      // 次のセグメントは前のセグメントよりも高いはず
      if(!(mc>mp+base*K3)){
         return(false);
      }
   }
   return(true);
} 

関数内ではジグザグのすべてのセグメントが確認されます。各セグメントの中央が計算され、前のセグメントの中央と比較されます。各セグメントは、基本*K3の値によって前のセグメントよりも高くなければなりません。

CheckInclineDn()関数では条件が1つ違います。

if(!(mc<mp-base*K3)){
   return(false);
}

この条件を満たすために、後続の各セグメントは前のセグメントよりも低くなければなりません。

以下がCheckInclineHorizontally()関数です。

bool CheckInclineHorizontally(int li,double base){ 
   // 基本セグメントの中央
   double mb=(PeackTrough[li+1].Val+PeackTrough[li+2].Val)/2;        
   for(int v=1;v<N;v++){
      // 次の頂部のインデックス
      int vi=li+1+v*2;
      // 次のセグメントの中央
      double mc=(PeackTrough[vi].Val+PeackTrough[vi+1].Val)/2;
      // 次のセグメントの中央は基本セグメントの中央から 
      // あまりにもずれるべきではない
      if(MathAbs(mc-mb)>base*K3){
         return(false);
      }
   }                  
   return(true);
}

形と勾配の条件が満たされた場合、次のコードが実行されます。

if(CompletionType==Immediately){                    // Immediate entry
   UpArrowBuffer[i]=low[i];
   UpDotBuffer[i]=PeackTrough[CurCount-1].Val+l1;
}
else{                                               // レベルブレイクアウトの待機
   SetLevelParameters(1);
   SetTarget(1,li);
}

パターン補完がImmediatelyに設定されている場合、指標は矢印を描き、目標点を配置します。それ以外の場合、ブレイクアウトレベルはSetLevelParameters()関数、目標はSetLevelParameters()関数を使用して設定されます。

以下はSetLevelParameters()関数です。

void SetLevelParameters(int dir){
   CurLevel.dir=dir;   
   switch(CompletionType){
      case OneLastVertex:                            // 1つの点に基づく
          CurLevel.v=PeackTrough[CurCount-3].Val;
      break;
      case TwoLastVertices:                          // 2つの点に基づく
         CurLevel.x1=PeackTrough[CurCount-5].Bar;
         CurLevel.y1=PeackTrough[CurCount-5].Val;
         CurLevel.x2=PeackTrough[CurCount-3].Bar;
         CurLevel.y2=PeackTrough[CurCount-3].Val;
      break;
   }
} 

SetLevelParameters()関数では、レベルパラメータの格納にSLevelParameters構造体が使用されます。

struct SLevelParameters{
   int x1;
   double y1;
   int x2;
   double y2;       // x1からy2 - 勾配レベルパラメータ
   double v;        // 水平レベルの値
   int dir;         // 方向
   double target;   // 目標
   // 勾配レベルの値を算出するメソッド
   double y3(int x3){
      if(CompletionType==TwoLastVertices){
            return(y1+(x3-x1)*(y2-y1)/(x2-x1));
      }
      else{
         return(v);
      }
   }
   // パラメータを初期化またはリセットするメソッド
   void Init(){
      x1=0;
      y1=0;
      x2=0;
      y2=0;
      v=0;
      dir=0;   
   }
};

この構造体には、行パラメータのフィールドx1、y1、x2、y2、水平レベル値'v' 、パターン方向'd'、目標'target'が含まれています。目標は、価格レベル(FromVertexToVertexが使用される場合)またはブレイクアウトレベル値(OneVertexまたはTwoVerticesが使用される場合)として設定できます。y3()メソッドは、勾配レベルの値を計算するために使用されます。Init()メソッドは、値の初期化またはリセットに使用されます。

すべてのパターン形成条件が満たされるとSetLevelParameter()関数が呼び出されます。この関数では、選択されたレベルタイプ(水平または傾斜)に応じて、勾配レベル(フィールドx1、y1、x2、y2)または水平レベル値'v'のパラメータが設定されます。y3()メソッドでは、フィールドx1、y1、x2、y2を使用してレベル値が計算されるか、 'v'フィールドの値が返されます。

指標ではSLevelParameters型の2つの変数が宣言されています。

SLevelParameters CurLevel;
SLevelParameters PreLevel;

この変数のペアは、変数CurCount-PreCountとCurDir-PreDirのペアと同様に使用され、変数の値は指標の初期計算の前にリセットされます(コードはOnTick()関数の冒頭にあります)。

int start;

if(prev_calculated==0){           // すべてのバーの最初の計算
   start=1;      
   CurCount=0;
   PreCount=0;
   CurDir=0;
   PreDir=0;  
   CurLevel.Init();    
   CurLevel.Init();
   LastTime=0;
}
else{                           // 新しいバーと形成中のバーの計算
   start=prev_calculated-1;
}


各バーの計算中に、これらの変数の値が移動されます(コードは指標ループの初めにあります)。

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

目標パラメータは、SetTarget()関数を呼び出すことによって設定されます。

void SetTarget(int dir,int li){
   switch(Target){
      case FromVertexToVertex:
         // 「頂点から頂点まで」版
         if(dir==1){
            CurLevel.target=PeackTrough[CurCount-1].Val+(PeackTrough[li+1].Val-PeackTrough[li].Val);
         }
         else if(dir==-1){
            CurLevel.target=PeackTrough[CurCount-1].Val-(PeackTrough[li].Val-PeackTrough[li+1].Val);
         }
      break;
      case OneVertex:
         // 1頂点の使用
         CurLevel.target=MathAbs(PeackTrough[li].Val-PeackTrough[li+2].Val);
      break;
      case TwoVertices:
         // 2頂点の使用
         SetTwoVerticesTarget(dir,li);
      break;
   }
}

FromVertexToVertexでは価格値が計算されます。OneVertexでは、ブレイクアウトレベルから目標への価格移動の値が'target' フィールドに割り当てられます。SetTwoVerticesTarget()関数ではSetTwoVerticesTarget が計算されます。

void SetTwoVerticesTarget(int dir,int li){
   // パターンの最初の下から上への
   // (長い)線の座標  
   double x11=PeackTrough[li].Bar;
   double y11=PeackTrough[li].Val;
   double x12=PeackTrough[li+1].Bar;
   double y12=PeackTrough[li+1].Val;
   // 買いのために2つの底部に引かれた線の座標
   // または売りのために2つの頂部に引かれた線の座標
   double x21=PeackTrough[li+2].Bar;
   double y21=PeackTrough[li+2].Val;
   double x22=PeackTrough[li+4].Bar;
   double y22=PeackTrough[li+4].Val;
   // 線が交差したときの値
   double t=TwoLinesCrossY(x11,y11,x12,y12,x21,y21,x22,y22);
   // 方向に応じて目標値を設定する
   if(dir==1){
      CurLevel.target=t-PeackTrough[li].Val;
   }
   else if(dir==-1){
      CurLevel.target=PeackTrough[li].Val-t;         
   }
}

SetTwoVerticesTargetバージョンではOneVertexと同様に、 ブレイクアウトレベルから目標への価格移動の値が'target'フィールドに割り当てられます。

価格とレベルのトラッキングがどのように行われているかを考えてみましょう(CompletionTypeはImmediatelyとは等しくありません)。

// レベルが使用される
if(CompletionType!=Immediately){
   // ジグザグが向きを変えた
   if(PeackTrough[CurCount-1].Bar==i){
      if(CurLevel.dir==1){                // 上向きブレイクアウトの待機
         // cl変数にレベル値を割り当てる
         double cl=CurLevel.y3(i); 
         // ジグザグがレベルを超える
         if(PeackTrough[CurCount-1].Val>cl){
            // 矢印を上向きに設定
            UpArrowBuffer[i]=low[i];
            // 目標点の設定
            if(Target==FromVertexToVertex){
               // 'target' フィールドの価格
               UpDotBuffer[i]=CurLevel.target;                        
            }
            else{
               // 'target'フィールドのレベルからの距離
               UpDotBuffer[i]=cl+CurLevel.target;
            }
            // 'dir'フィールドをゼロにしてレベルの追跡を停止
            CurLevel.dir=0;
         }
      }
      else if(CurLevel.dir==-1){         // 下向きブレイクアウトの待機
         // cl変数にレベル値を割り当てる
         double cl=CurLevel.y3(i);
         // ジグザグがレベルを超える
         if(PeackTrough[CurCount-1].Val<cl){
            // 矢印を下向きに設定
            DnArrowBuffer[i]=low[i];
            // 目標点の設定
            if(Target==FromVertexToVertex){
               // 'target' フィールドの価格
               DnDotBuffer[i]=CurLevel.target;
            }
            else{                     
               // 'target'フィールドのレベルからの距離
               DnDotBuffer[i]=cl-CurLevel.target;
            }
            // 'dir'フィールドをゼロにしてレベルの追跡を停止
            CurLevel.dir=0;
         }         
      }         
   }
} 

これはジグザグが方向を変えるたびに確認されます。すべてのジグザグピークはPeackTrough配列に格納され、最後のジグザグポイントのインデックスと現在のバーインデックスの対応によって決定されます。

if(PeackTrough[CurCount-1].Bar==i){

現在のレベル値はy3()メソッドを使用して計算されます。

double cl=CurLevel.y3(i); 

最後のジグザグセグメントがこのレベルを超えたかどうかが確認されます。

if(PeackTrough[CurCount-1].Val>cl){

レベルを超えた場合、指標は矢印を描き、目標点を追加します。'target'フィールドは、目標値を含むことができ、この場合、値は直接使用されます。また、目標までの距離を含むこともでき、この場合、目標は現在のレベル値に基づいて計算されます。 

if(Target==FromVertexToVertex){
   UpDotBuffer[i]=CurLevel.target;                        
}
else{
   UpDotBuffer[i]=cl+CurLevel.target;
}

最後に、'dir'フィールドがリセットされて、次のパターンが表示されるまで価格の追跡を停止します。

CurLevel.dir=0;

指標作成はこれで終わりです。その操作のいくつかの断片は図15に示されています。


図15 iHorizontalFormation指標シグナル

指標にアラート機能が追加されました。この指標はすぐに使用でき、以下の添付ファイルで利用できます。ファイル名はiHorizontalFormationです。


フラグ、ペナントとウェッジを検索するための汎用指標

次に、2番目のカテゴリパターンを検索するための指標を作成します。それらの形状は、パターン領域を埋めるバーによって形成されます。パターンは強い価格の動きから始まります。この場合、それは長いバーから始まり、バーを特定するためには長い期間を持つATR指標を使用します。バーは、髭がATR値に係数を掛けた値を超えると、長いとみなされるので、ATR期間及び係数のための外部パラメータが必要です。

input int                  ATRPeriod            =  50;
input double               K1                   =  3;

ATRハンドル用のグローバル指標変数を宣言しましょう。

int h;

OnInit()関数では、ATR指標を読み込んでハンドルを取得します。

h=iATR(Symbol(),Period(),ATRPeriod);
if(h==INVALID_HANDLE){
   Alert("Error load indicator");
   return(INIT_FAILED);
}

指標のメインループでATR値を取得します。

double atr[1];
if(CopyBuffer(h,0,rates_total-i-1,1,atr)==-1){
   return(0);
}

ATRの値を使用してバーサイズを確認します。バーの髭の大きさが係数によって設定された閾値を超えている場合、予想される価格の動きの方向を特定する必要があります。方向は、色によって特定されます(「始値」及び「終値」に基づく)。終値が始値を上回る場合、価格のさらなる上昇が予想されます。終値が始値を下回る場合、さらなる下落が予想されます。

if(high[i]-low[i]>atr[0]*K1){    // 長いバー
   if(close[i]>open[i]){         // バーが上向きである
      Cur.Whait=1;
      Cur.Count=0;
      Cur.Bar=i;
   }
   else if(close[i]<open[i]){    // バーが下向きである
      Cur.Whait=-1;   
      Cur.Count=0;
      Cur.Bar=i;
   }
}


バーサイズと方向の条件が満たされると、Cur構造体のフィールドが適切な値に設定されます。期待される方向がWhaitフィールドに設定(上は1、下は-1)されます。Countフィールドがリセットされて0の値が割り当てられます。このフィールドは、パターン内のバーを数えるために使用されます。パターンの最初(長い)バーのインデックスが 'Bar'フィールドに保存されます。

Cur構造体を分析しましょう。この構造体には、3つのフィールドと、すべてのフィールドを高速リセットするInit()メソッドがあります。

struct SCurPre{
   int Whait;
   int Count;
   int Bar;
   void Init(){
      Whait=0;
      Count=0;
      Bar=0;
   }
};

この型の2つの静的変数とdatetime型の変数は、OnTick()関数の初めに宣言されます。

static datetime LastTime=0;   
static SCurPre Cur;          
static SCurPre Pre;

次に、指標計算が開始される最初のバーのインデックスを計算しCur及びPre変数を初期化します。

int start=0;

if(prev_calculated==0){           // 指標の初めの計算
   
   start=1;      
   
   Cur.Init();
   Pre.Init();             
     
   LastTime=0;      
}
else{                             // 新しいバーと形成中のバーの計算
   start=prev_calculated-1;
}

Cur及びPre変数の値は、指標の主要ループの初めに移動されます。

if(time[i]>LastTime){       // バーの初めの計算
   LastTime=time[i];
   Pre=Cur;              
}
else{                      // バーの再計算
   Cur=Pre;
}  

このメソッドど変数(PreCountとCurCount)はWolfe波動稿で詳しく説明されています。この記事では、これはiHorizontalFormation指標(Cur及びPre接頭辞付きの変数)を作成するときに使用されました。

Cur.Count変数がゼロでない場合、指標はパターン検出のための条件をより厳しくします。パターンを構成するバーの数が数えられ、CurCount変数が増加します。長いバーの後の最初のバーは抜かされ、3番目のバーからが確認されます。

if(Cur.Whait!=0){
   Cur.Count++;            // バーの数を数える
   if(Cur.Count>=3){
      // さらなる確認

   }
}

さらなる確認がされたことは主にバーの重なりによってわかります(図16)。

図16 2つのバーの重なりLは最も低い高値と最も高い安値との差として定義される

重なりは2つのバーを使用して計算され、最も低い「高値」と最も高い「安値」の差に等しくなります。 

Overlapping=MathMin(high[i],high[i-1])-MathMax(low[i],low[i-1]);

最初のバーとの重なりは確認されないので、重なりの確認は2番目のバーからではなく3番目のバーから始まります。

2つのバーの重なりは、閾値を超えなければなりません。値がポイントで設定されている場合、指標の作業結果は時間枠に強く依存します。これは、パラメータの値が異なる時間枠で大きく異なるためです。時間枠に依存しないようにするために、2つのバーの最も長い髭を使用して確認されたバーの基準値を定義しましょう。

double PreSize=MathMax(high[i-1]-low[i-1],high[i]-low[i]);

バーの重なりの値を確認します。

if(!(Overlapping>=PreSize*MinOverlapping))

2つのバーが重なっていない場合、一連の連続する重なり合うバーが上にあるとみなされます。この場合、行のバーの数を確認します。

if(Cur.Count-2>=MinCount){
   // さらなる確認
}
Cur.Whait=0;

行のバーの数がMinCount変数の値を超えると、更なる確認がとられます。さもなければ、パターン形成の待機はキャンセルされてCurCount変数がゼロにされます。上記のコードでは、条件を確認するときに、CurCount変数から2が減算されます。つまり、最初のロングバーとの重なり条件が満たされていない最終バーは考慮されません。

MinOverlapping及びMinCountは指標の外部変数です。

input double               MinOverlapping       =  0.4;
input int                  MinCount             =  5;

重なり合うバーの数の条件が満たされると、パターンの形と勾配がをさらに確認されます。まず、重なり合ったバーの見つかったシリーズのパラメータを決定します。  

double AverSize,AverBias,AverSizeDif;
PatternParameters(high,low,i-1,Cur.Count-2,AverSize,AverBias,AverSizeDif);

パラメータはPatternParameters()関数で決定され、AverSize、AverBias、AverSizeDifの各変数で参照によって返されます。AverSizeでは平均バーサイズ、AverBiasではバー中心の平均シフト、AverSizeDifでは隣接する2つのバーの平均サイズの差が返されます。これらのパラメータの計算方法を理解するために、PatternParameters()関数を詳細に検討してみましょう。

void PatternParameters( const double & high[],
                        const double & low[],
                        int i,
                        int CurCnt,
                        double & AverSize,
                        double & AverBias,
                        double & AverSizeDif
){
            
   // バーサイズの平均            
   AverSize=high[i-CurCnt]-low[i-CurCnt];
   // バーのシフトの平均
   AverBias=0;
   // 2つの隣接するバーのサイズの平均差
   AverSizeDif=0;
   
   for(int k=i-CurCnt+1;k<i;k++){      // 最初のものを除いたシリーズのバー
      // 平均サイズ
      AverSize+=high[k]-low[k];
      // 平均シフト
      double mc=(high[k]+low[k])/2;
      double mp=(high[k-1]+low[k-1])/2;
      AverBias+=(mc-mp);
      // サイズの平均差
      double sc=(high[k]-low[k]);
      double sp=(high[k-1]-low[k-1]);
      AverSizeDif+=(sc-sp);               
      
   }
   
   // 和を分量で除算
   AverSize/=CurCnt;
   AverBias/=(CurCnt-1);
   AverSizeDif/=(CurCnt-1); 
} 

この関数には、2つの矢印 'high'と 'low'、重なりが終了するバーのインデックス、系列の長さ、戻り値の3つの変数のデータが渡されます。値はforループで計算されます。AverBiasとAverDiffは隣接する2つのバーについて計算されるため、系列の最初のバーは抜かされます。

for(int k=i-CurCnt+1;k<i;k++)

したがって、ループが始まる前にAverBiasとAverDiffがリセットされ、AverSize変数はループで抜かされたバーに基づいて計算された値に設定されます。

バーサイズはループ内でAverSizeに追加されます。

AverSize+=high[k]-low[k];

AverBias(シフト)では、バーの間の中心点が計算され、次にそれらの差が計算され、結果の差が要約されます。

double mc=(high[k]+low[k])/2;
double mp=(high[k-1]+low[k-1])/2;
AverBias+=(mc-mp);

AverSizeDifでは、隣接するバーのサイズとそれらの差が計算され、結果の差が要約されます。

double sc=(high[k]-low[k]);
double sp=(high[k-1]-low[k-1]);
AverSizeDif+=(sc-sp);    

ループの後で、合計が合計に使われた値の数で除算されます。

AverSize/=CurCnt;
AverBias/=(CurCnt-1);
AverSizeDif/=(CurCnt-1); 

パラメータ計算の後には、パターンの形が確認されます。この確認は予想価格の動きの方向に依存しません。形は、収縮する形(ペナント)にはFormTapered()、長方形(フラグ)にはFormHorizontal()、膨張する形(ウェッジ)にはFormExpanding() の3つの関数を使用して確認されます。

if(   FormTapered(AverSizeDif,AverSize) ||
      FormHorizontal(AverSizeDif,AverSize) ||
      FormExpanding(AverSizeDif,AverSize)
){ 
   // 方向の確認
}

iHorizontalFormation指標の設定では、3つの形のうちの1つだけを選択できますが、ここでは3つの形は個別に使用されます。これは、条件がより稀に満たされ、取引シグナルもまれであるという事実によるものです。各パターンは、指標パラメータの3つの変数によって有効/無効にすることができます。さらに、形にはそれぞれプロパティウィンドウで係数が用意されています。

input bool                 FormTapered          =  true;
input double               FormTaperedK         =  0.05;
input bool                 FormRectangular      =  true;
input double               FormRectangularK     =  0.33;
input bool                 FormExpanding        =  true;
input double               FormExpandingK       =  0.05;

 形を確認する関数を分析しましょう。以下はFormTapered()関数です。

bool FormTapered(double AverDif, double AverSize){
   return(FormTapered && AverDif<-FormTaperedK*AverSize);
}

バーサイズの平均差が負の閾値よりも小さい場合、バーサイズは縮小していると考えられ、これはパターンの収縮する形に対応します。

以下はFormHorizontal()関数です。

bool FormHorizontal(double AverDif, double AverSize){
   return(FormRectangular && MathAbs(AverDif)<FormRectangularK*AverSize);
}

バーサイズの平均差が負の閾値よりも小さい場合、バーは等しいサイズを有すると考えられ、これはパターンが長方形であることに対応します。

以下はFormExpanding()関数です。

bool FormExpanding(double AverDif, double AverSize){
   return(FormExpanding && AverDif>FormExpandingK*AverSize);
}

この関数では、収縮パターンとは対照的に、バーサイズの平均差は正の閾値を超えていなければなりません。これはバーの増加と形の膨張に相当します。

形が条件を満たすと、パターンの勾配が確認されます。この確認は予想価格の動きの方向に依存し、上向きにはCheckInclineForBuy()、下向きにはCheckInclineForSell()が使われます。

if(Cur.Whait==1){
   if(CheckInclineForBuy(AverBias/AverSize)){
      // 上向きの場合の追加条件確認

   }
}
else if(Cur.Whait==-1){
   if(CheckInclineForSell(AverBias/AverSize)){   
      // 下向きの場合の追加条件確認

   }
}

勾配確認オプションは、形の確認オプションと同様に個別に有効化されます。適切な変数は、プロパティウィンドウで使用できます。プロパティウィンドウの各勾配オプションには別々の係数が使用できます。

input bool                 InclineAlong         =  true;
input double               InclineAlongK        =  0.1;
input bool                 InclineHorizontal    =  true;
input double               InclineHorizontalK   =  0.1;
input bool                 InclineAgainst       =  true;
input double               InclineAgainstK      =  0.1;


以下がCheckInclineForBuy()関数です。

bool CheckInclineForBuy(double Val){
   return(  (InclineAlong && Val>InclineAlongK) || 
            (InclineHorizontal && MathAbs(Val)<InclineHorizontalK) || 
            (InclineAgainst && Val<-InclineAgainstK)
   );
}   

バーの相対的なシフトの値であるAverBias/AverSizeが関数に渡されます。パターンの勾配は、これが正の閾値を上回っている場合は上向き、 負の閾値を下回っている場合は下向きであると考えられます。符号を考慮せずに値が閾値内にある場合、パターンは水平になります。

bool CheckInclineForBuy(double Val){
   return(  (InclineAlong && Val>InclineAlongK) || 
            (InclineHorizontal && MathAbs(Val)<InclineHorizontalK) || 
            (InclineAgainst && Val<-InclineAgainstK)
   );
}   

同様に、下記は下向きの場合です。

bool CheckInclineForSell(double Val){
   return(  (InclineAlong && Val<-InclineAlongK) || 
            (InclineHorizontal && MathAbs(Val)<InclineHorizontalK) || 
            (InclineAgainst && Val>InclineAgainstK)
   );
}  

ここで下向きの勾配は移動方向に対応し、上向きの勾配は逆方向のしるしです。

最後に確認されるのは最終バーの方向です。これには、最終バーがパターンに沿った方向を向くかパターンに反した方向に向くかという2つのバリエーションがあります。この最後の確認のバリアントは、プロパティウィンドウの以下のパラメータによって有効にすることができます。

input bool                 EnterAlong           =  true;
input bool                 EnterAgainst         =  true;

上方向の確認は次のように行います。

if((EnterAlong && close[i]>open[i]) || (EnterAgainst && close[i]<open[i])){
   Label1Buffer[i]=low[i];
   Label3Buffer[i]=close[i]+(high[Cur.Bar]-low[Cur.Bar]);
}

EnterAlongが選択されていてバーが上を向いている場合やEnterAgainstが選択されていてバーが下を向いている場合は、指標は矢印と目標点を描画します。目標は初期の大きなバーのサイズに等しい距離にあります。

同様に、下記は下向きの場合です。

if((EnterAlong && close[i]<open[i]) || (EnterAgainst && close[i]>open[i])){
   Label2Buffer[i]=high[i];                  
   Label4Buffer[i]=close[i]-(high[Cur.Bar]-low[Cur.Bar]);
}

この指標はこれで完成したと考えることができます。この指標はアラート機能付きで、以下の添付ファイルですぐ使用できます。ファイル名はiFlagです。 


テスター指標

指標の効率をテストする最も簡単で便利な方法は、ストラテジーテスターでエキスパートアドバイザーを実行することです。Wolfe波動稿では、著者は簡単なエキスパートアドバイザーを作成しました。本稿で作成した指標をテストするにはそのEAを少し変更すればいいです。iHorizontalFormation及びiFlag指標のバッファの索引付けは、iWolfeWavesバッファの索引付けに対応します。したがって、エキスパートアドバイザーの外部パラメータとiCustom()呼び出しを変更するだけで済みます。

指標をテストするには、その効果を即座に評価することができるもう1つの面白い方法があります。テスター指標です。メイン指標の矢印に基づく取引が追加指標でシミュレートされ、エクイティ及びバランスを示す線がチャートに表示されます。

テスター指標を作成する最も簡単かつ最も明白なアプローチは、iCustom()関数を使用することです。しかし、このアプローチには重大な欠点があります。それは、矢印を描画するメイン指標が価格チャートウィンドウに表示される一方、テスター指標が描画するエクイティ及びバランスの線はサブウィンドウに表示されるということです。それなので、同じパラメータを持つ2つの指標をチャートで実行しなければなりません。後にパラメータを変更する必要がある場合は、変更は2つの指標に対して行う必要があり、これは便利ではありません。

2番目のバリアントは、テスター指標にチャート上にグラフィックオブジェクトとして矢印を描画させることです。

3番目のバリアントはChartIndicatorAdd()関数を使用することです。この関数を使うとチャートに別の指標を取り付けることができます。この場合、メイン指標のパラメータを変更するたびに、チャート上の追加のテスター指標を見つけて削除し、新しいパラメータで再起動する必要があります。これは満足できる便利なオプションです。

しかし、4番目のバリアントもあります。これは第3のバリアントと比べて便利さに欠け、実装面ではさらに簡単です。また、1つの汎用テスター指標を作成し、iHorizontalFormation指標とiFlag指標の両方にわずかな変更を加えて使用することができます。

iHorizontalFormationとiFlagの変更は、外部ID変数を作成する必要性と関連しています。 

input int                  ID             =  1;

次に、この変数を使ってOnInitで短い指標名を設定します。

string ShortName=MQLInfoString(MQL_PROGRAM_NAME)+"-"+IntegerToString(ID);
IndicatorSetString(INDICATOR_SHORTNAME,ShortName);

短い名前は指標ファイル名、 " - "記号とID変数の値で構成されています。テスター指標は、この短い名前を使用してメイン指標を見つけ、そのハンドルを取得することができます。

iHorizontalFormation指標はジグザグに基づいており、高値-安値、終値、または他の指標を使用して計算できます。高値-安値を使用して計算すると、描画された矢印はチャートから消えません。この指標を取引に使用すると、シグナルを現在形成中のバーで分析することができます。その他の場合では、終値と他の指標を使用してジグザグを計算する場合には、既に形成されたバーで矢印を追跡する必要があります。よって、どのバーで矢印を確認すべきかをテスター指標に知らせる必要があります。

The iHorizontalFormation指標とiFlag指標は、テイクプロフィットに使用できる目標点を描画します。しかし、iHorizontalFormationでは、これはジグザグが価格を使用して計算される場合にのみ可能です。したがって、目標点を使用すべきか追加のテイクプロフィットとストップロスパラメータを使用すべきかどうかについて、テスター指標に知らせる必要があります。最初のアイデアは、グローバル変数を使用してデータを渡すことです。しかし、MetaTrader 5ターミナルには、指標パラメータが変更されると新しいハンドルを持つ指標の新しいインスタンスがロードされるが、直前のインスタンスはすぐにはメモリからアンロードされないという特長があります。したがって、指標の外部パラメータを返すと、指標の新しいロードと計算は実行されません。つまり、OnInit()関数は実行されず、prev_calculated変数はリセットされません。その結果、グローバル変数は新しい値を受け取りません。 

テスター指標に必要なパラメータは指標バッファを使用して渡されます。これには1つのバッファ要素で十分です。最初の要素と最も左の要素である既存の要素を使用します。2つの値を渡す必要があります。そのうちの1つは、テスター指標が、完全に形成されたバーまたは現在形成中のバー上のメイン指標を分析すべきかどうかを決定します。第2の値は、ポジションのテイクプロフィットの目標点を使用するかどうかを設定します。prev_calculate=0の場合以下のコードがOnCalculate() に追加されます。

int ForTester=0;       // 値のための変数
if(!(SrcSelect==Src_HighLow)){
   // 完全に形成されたバーで操作する
   ForTester+=10;
}   
if(!(SrcSelect==Src_HighLow || SrcSelect==Src_Close)){
   // 目標点が使用できる
   ForTester+=1;
}     
UpArrowBuffer[0]=ForTester;  

1つの配列要素を使って10以下の2つの値を渡さなければならないので1番目の値に10を掛けてそれに2番目の値を足します。渡される数値は0または1の値しか持てないため、基数が2の数値を使用できますが、転送されるデータの量は重要ではないため、バイトを節約する必要はありません。

iFlag指標についても同様の変更が必要です。OnInit()関数では下記です。

string ShortName=MQLInfoString(MQL_PROGRAM_NAME)+"-"+IntegerToString(ID);
IndicatorSetString(INDICATOR_SHORTNAME,ShortName);


OnCalculate()では下記です。

Label1Buffer[0]=11;

iFlag指標は完全に形成されたバー上で常に分析することができ、その目標点は常に使用できます。したがって、値11は計算なしで割り当てられます。

完全に形成されたバーを使用する場合、明らかに新しいバーが開かれるたときにポジションが開かれます。しかし、現在形成中のバーにエントリーする場合、エントリー価格は不明です。そこでiHorizontalFormationが変更され、矢印が付いたバーの点を描画するバッファの追加されました。このバッファにはポジションオープンレベルが指定されています。

ここからはテスター指標で直接作業します。iTesterという新しい指標を作成し、外部パラメータを追加します。

input int                  ID             =  1;
input double               StopLoss_K     =  1;
input bool                 FixedSLTP      =  false;
input int                  StopLoss       =  50;
input int                  TakeProfit     =  50;

ここで

  • IDはメイン指標の識別子
  • StopLoss_Kは、目標点を使用する際のテイクプロフィット値に基づいてストップロスを計算するために使用される係数
  • FixedSLTPは、StopLoss及びTakeProfit変数を使用するか目標点とStopLoss_K変数を使用かを意味する

テスター指標が左上隅に名前だけでなく使用している矢印の指標の名前も表示すると非常に便利ですが、テスト対象指標がまだチャートに取り付けられていない間は、テスター指標の名前が表示されます。グローバル変数を宣言します。

string IndName;

OnInit()では、これにテスト指標の名前を割り当てます。

IndName=MQLInfoString(MQL_PROGRAM_NAME);

テスター指標によって開かれた各ポジションに関する情報はSPos構造体の配列に格納され、PosCnt変数はポジションの数を数えるために使用されます。

struct SPos{
   int dir;
   double price;
   double sl;
   double tp;
   datetime time; 
};
SPos Pos[];
int PosCnt;

次の変数を使用して、一度だけ配列に取引を追加する必要があります。

datetime LastPosTime;

配列にポジションを追加するときは、LastPosTime変数からの時刻と、ポジションが開かれたバーの時間がチェックされます。ポジションを追加するときは、LastPosTime変数に新しい時刻が設定されます。バーの時間がLastPosTimeの時刻と等しい場合、ポジションは既に開いています。

ポジションを決済する、つまり利益を計算するには、さらに2つの変数が必要です。

int Closed;
datetime CloseTime;

1つのバーで決済されたポジションの利益はClosed変数に割り当てられ、このバーの時刻はCloseTimeに割り当てられます。さらに、それがどのように機能するかを詳しく見ていきます。

補助変数すべてとOnInit()関数についてはすでにお話ししましたので、OnCalculate()関数に進むことができます。次の補助変数を宣言します。

string name;
static int last_handle=-1;
static int shift=0;
static int use_target=0;
int handle=-1;     
int start=2;  

下記は変数の説明です。

  • nameChartIndicatorName()で指標名を取得するのに使用されます。
  • 静的変数last_handleは、テスト対象指標のハンドルを格納するために使用されます。
  • 静的変数shiftuse_targetはテスト対象指標から渡されるパラメータに使用されます。
  • handleChartIndicatorGet()でハンドルを受け取るのに使用されます。
  • start は指標計算を開始できるようにします。

テスト対象指標を検索するコードを分析しましょう。まず、価格チャートに取り付けられている指標の数を特定します。

int it=ChartIndicatorsTotal(0,0);

ループを使用します。

for(int i=0;i<it;i++){        // チャート上のすべての指標
   // 次の指標の名前を取得する
   name=ChartIndicatorName(0,0,i);
   // 部分文字列 "-"を検索する
   int p=StringFindRev(name,"-");
   if(p!=-1){
      // 部分文字列が見つかると、識別子の値が確認される
      if(StringSubstr(name,p+1,StringLen(name)-p-1)==IntegerToString(ID)){
         // IDが対応するのでハンドルを取得する
         handle=ChartIndicatorGet(0,0,name);
      }
   }
} 

上記のコード部分をより詳細に考えてみましょう。'name'変数には、与えられたインデックスでの指標名を返すChartIndicatorName()関数を使用して受け取った指標の名前が割り当てられます。受け取った名前とIDとの対応を確認します。そのために、部分文字列 " - "の最後の発生が検索されます。" - "が見つかった場合、それに続く文字列の部分が抽出されます。それが識別子に対応する場合、 'handle'変数は、指標名によってハンドルを返すChartIndicatorGet()関数によって取得されたハンドル値を取得します。 

取得されたハンドルはlast_handle変数の既知のハンドルと比較されます(変数は静的です。つまり、その値はOnCalculate()関数の終了後にも保持されます)。

if(handle!=last_handle){
   if(handle==-1){                    // ハンドルなし
      // ソース名の設定
      IndicatorSetString(INDICATOR_SHORTNAME,IndName);
      ChartRedraw(0);
      return(0);
   }
   // テスト対象指標の計算が完了したかの確認
   int bc=BarsCalculated(handle);
   if(bc<=0)return(0);                // さもないと関数が中断される
   // データとテストパラメータのコピー
   double sh[1];
   if(CopyBuffer(handle,0,rates_total-1,1,sh)==-1){
      // コピーが失敗すると、関数は次のティックまでl
      // 中断される
      return(0);
   }
   // 別のパラメータの抽出
   shift=((int)sh[0])/10;              // 完成したまたは形成中のバー
   use_target=((int)sh[0])%10;         // 目標点が利用可能?
   last_handle=handle;                 // ハンドル値の保存
   // 指標名の設定
   IndicatorSetString(INDICATOR_SHORTNAME,name);
   ChartRedraw(0);
}
else if(prev_calculated!=0){
   // 新しいハンドルがない場合、新しいバーと 
   // 現在形成中のバーのみが計算される
   start=prev_calculated-1;
}

'handle'変数の値がlast_handleの値と等しくない場合はテスト対象指標が変更されています。チャートに取り付けられたばかりなのか、そのパラメータが編集されている可能性があります。また、チャートから削除されたのかもしれません。指標がチャートから削除された場合、 'handle'変数は-1になり、テスター指標はデフォルト名に設定され、OnCalculate()関数は終了します。ハンドル変数に有効なハンドル値がある場合は、形成中/完成したバーと目標点を使用する権限が取得されます。OnCalculate()実行が続くと、 'handle'及び 'last_handle'は等しく、 'start'変数、すなわち計算が開始される初期バーの通常の計算が実行されます。

'start'変数のデフォルト値は2です。指標の完全な再計算が必要な場合(ハンドルが変更されたとき、またはprev_calculatedが0に等しいときに必要)、いくつかの追加の変数をリセットする必要があります。

if(start==2){
   PosCnt=0;
   BalanceBuffer[1]=0;
   EquityBuffer[1]=0;
   LastPosTime=0;
   Closed=0;
   CloseTime=0;      
}

ポジション数(PosCnt)、バランスとエクイティのための指標バッファの最初の要素( BalanceBuffer[1]EquityBuffer[1])、最後のポジションの時刻(LastPosTime)、同じバーで決済されたポジションの利益(Closed)と決済時刻(ClosedTime)の要素は0にリセットせれます。

ここで、指標のメインループを分析してみましょう。以下はコメント付きの完全なコードで、行単位の分析が続きます。

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

   // 既知のバランスとエクイティを渡す
   BalanceBuffer[i]=BalanceBuffer[i-1];
   EquityBuffer[i]=EquityBuffer[i-1];

   if(CloseTime!=time[i]){ 
      // 新しいバーの計算の開始
      Closed=0; // 利益のための変数をゼロにする
      CloseTime=time[i];          
   }

   // テスト対象指標のデータの取得
   double buy[1],sell[1],buy_target[1],sell_target[1],enter[1];
   int ind=rates_total-i-1+shift;
   if(CopyBuffer(last_handle,0,ind,1,buy)==-1 || 
      CopyBuffer(last_handle,1,ind,1,sell)==-1 ||
      CopyBuffer(last_handle,2,ind,1,buy_target)==-1 || 
      CopyBuffer(last_handle,3,ind,1,sell_target)==-1        
   ){
      return(0);
   }
  
   if(shift==0){
      // 形成中のバーでテストを実施する場合は、テスト対象指標の追加バッファから 
      // 始値を取得する       
      if(CopyBuffer(last_handle,4,ind,1,enter)==-1){
         return(0);
      } 
   }
   else{
      // 完成したバーでテストを実施する場合は 
      // バーの始値を使用する
      enter[0]=open[i];
   }

   // 買いのための矢印
   if(buy[0]!=EMPTY_VALUE){
      AddPos(1,enter[0],buy_target[0],spread[i],time[i],use_target);      
   }
   // 売りのための矢印
   if(sell[0]!=EMPTY_VALUE){
      AddPos(-1,enter[0],sell_target[0],spread[i],time[i],use_target);       
   }

   // ポジションを決済する必要があるかどうかを確認する
   CheckClose(i,high,low,close,spread);

   // バランスの線
   BalanceBuffer[i]+=Closed;
   
   // エクイティの線
   EquityBuffer[i]=BalanceBuffer[i]+SolveEquity(i,close,spread);

}

バランスの値は、以前に知られていたバランスと決済されたポジションの利益から形成されます。この目的のために、以前に知られたバランス値が、バッファの前の要素から渡されます。

// 既知のバランスとエクイティを渡す
BalanceBuffer[i]=BalanceBuffer[i-1];


各バーの最初の計算では、このバーで決済されたポジションの利益の変数がリセットされます。

if(CloseTime!=time[i]){ 
   Closed=0;
   CloseTime=time[i];          
}

テスト対象指標のデータがコピーされます。

// テスト対象指標のデータの取得
double buy[1],sell[1],buy_target[1],sell_target[1],enter[1];
int ind=rates_total-i-1+shift;
if(CopyBuffer(last_handle,0,ind,1,buy)==-1 || 
   CopyBuffer(last_handle,1,ind,1,sell)==-1 ||
   CopyBuffer(last_handle,2,ind,1,buy_target)==-1 || 
   CopyBuffer(last_handle,3,ind,1,sell_target)==-1        
){
   return(0);
}

矢印の付いたバッファのデータは、'buy'配列と'sell'配列にコピーされます。目標点のデータは、'buy_targetと'sell_target'にコピーされます。コピーする前に、バー変数 'ind'はシフト変数を考慮して計算されます。

'shift'の値に応じて、追加のバッファがコピーされるか、バーの開始価格が使用されます。

if(shift==0){
   // 形成中のバーでテストを実施する場合は、テスト対象指標の追加バッファから 
   // 始値を取得する       
   if(CopyBuffer(last_handle,4,ind,1,enter)==-1){
      return(0);
   } 
}
else{
   // 完成したバーでテストを実施する場合は 
   // バーの始値を使用する
   enter[0]=open[i];
}

計算されたバーに矢印が見つかった場合は、AddPos()関数を呼び出してポジションを開きます。

// 買いのための矢印
if(buy[0]!=EMPTY_VALUE){
   AddPos(1,enter[0],buy_target[0],spread[i],time[i],use_target);      
}
// 売りのための矢印
if(sell[0]!=EMPTY_VALUE){
   AddPos(-1,enter[0],sell_target[0],spread[i],time[i],use_target);       
}

ポジションを決済する必要があるかどうかはCheckClose()関数で確認されます。ポジションが決済される場合、得られる利益はClosed変数に記憶されます。

// ポジションを決済する必要があるかどうかを確認する
CheckClose(i,high,low,close,spread);

Closed変数からの利益がバランスに足されます。

// バランスの線
BalanceBuffer[i]+=Closed;

エクイティは、SolveEquity()関数で計算されたバランスと含み益から構成されます。

EquityBuffer[i]=BalanceBuffer[i]+SolveEquity(i,close,spread);

AddPos()、CheckClose()、SolveEquity()の各関数を考察しましょう。以下は、各関数の詳細なコメント付きのコードです。  

以下はAddPos()関数です。

void AddPos(int dir, double price,double target,int spread,datetime time,bool use_target){

   if(time<=LastPosTime){
      // 'time'時刻を持つポジションが追加されている
      return;
   }
   
   // 配列に空きがない
   if(PosCnt>=ArraySize(Pos)){
      // 配列サイズは32要素のブロックで増加する
      ArrayResize(Pos,ArraySize(Pos)+32);
   }
   
   // ポジションの方向が保存される
   Pos[PosCnt].dir=dir;
   // ポジションが開いた時刻
   Pos[PosCnt].time=time;
   // ポジションの始値
   if(dir==1){
      // 買いのための呼び値
      Pos[PosCnt].price=price+Point()*spread;  
   }
   else{
      // 売りのための呼び値
      Pos[PosCnt].price=price;  
   }

   // ストップロスとテイクプロフィットが計算される
   if(use_target && !FixedSLTP){ 
      // 目標点が使用されてストップロスが計算される
      if(dir==1){
         Pos[PosCnt].tp=target;
         Pos[PosCnt].sl=NormalizeDouble(Pos[PosCnt].price-StopLoss_K*(Pos[PosCnt].tp-Pos[PosCnt].price),Digits());
      }
      else{
         Pos[PosCnt].tp=target+Point()*spread;
         Pos[PosCnt].sl=NormalizeDouble(Pos[PosCnt].price+StopLoss_K*(Pos[PosCnt].price-Pos[PosCnt].tp),Digits());
      }   
   }
   else{
      // StopLoss変数とTakeProfit変数が使用される
      if(dir==1){
         Pos[PosCnt].tp=Pos[PosCnt].price+Point()*TakeProfit;
         Pos[PosCnt].sl=Pos[PosCnt].price-Point()*StopLoss;
      }
      else{
         Pos[PosCnt].tp=Pos[PosCnt].price-Point()*TakeProfit;
         Pos[PosCnt].sl=Pos[PosCnt].price+Point()*StopLoss;
      }     
   }
   
   PosCnt++;
   
}  

以下はCheckClose()関数です。

void CheckClose(int i,const double & high[],const double & low[],const double & close[],const int & spread[]){
   for(int j=PosCnt-1;j>=0;j--){                                       // All positions
      bool closed=false;                                               // 'false'はポジションが開いていることを意味する
      if(Pos[j].dir==1){                                               // 買い
         if(low[i]<=Pos[j].sl){                                        // 価格がストップロス以下
            // ポイント単位の利益
            Closed+=(int)((Pos[j].sl-Pos[j].price)/Point());
            closed=true;                                               // インデックスjを持つポジションが決済された
         }
         else if(high[i]>=Pos[j].tp){                                  // テイクプロフィットに達した
            // ポイント単位の利益
            Closed+=(int)((Pos[j].tp-Pos[j].price)/Point());    
            closed=true;                                               // インデックスjを持つポジションが決済された
         }
      }
      else{ // 売り
         if(high[i]+Point()*spread[i]>=Pos[j].sl){                     // ストップロスに達した
            // ポイント単位の利益
            Closed+=(int)((Pos[j].price-Pos[j].sl)/Point());
            closed=true;                                               // インデックスjを持つポジションが決済された
         }
         else if(low[i]+Point()*spread[i]<=Pos[j].tp){                 // 価格がテイクプロフィット以下
            // ポイント単位の利益
            Closed+=(int)((Pos[j].price-Pos[j].tp)/Point());              
            closed=true;                                               // インデックスjを持つポジションが決済された
         }         
      }
      // ポジションが決済されたので配列から削除するべき
      if(closed){ 
         int ccnt=PosCnt-j-1;
         if(ccnt>0){
            ArrayCopy(Pos,Pos,j,j+1,ccnt);
         }
         PosCnt--;
      }
   }
}

CheckClose()関数では、Pos配列に保存されているすべてのポジションが確認され、Stop LossとTake Profitの値が現在の高値または安値と比較されます。ポジションが決済されている場合は、ポイント単位での利益がClosed変数に追加されます。その後、配列からポジションが削除されます。

以下はSolveEquity()関数です。

int SolveEquity(int i,const double & close[],const int & spread[]){
   int rv=0;                                // 結果を示す変数
   for(int j=PosCnt-1;j>=0;j--){            // すべてのポジション
      if(Pos[j].dir==1){                    // 買い
                                            // 利益
         rv+=(int)((close[i]-Pos[j].price)/Point());
      }
      else{                                // 売り
         // 利益
         rv+=(int)((Pos[j].price+Point()*spread[i]-close[i])/Point());         
      }
   }
   return(rv);
}  


SolveEquity()関数は、現在の終値を考慮して、Pos配列からのすべてのポジションの利益を計算します。

iTester指標の分析はこれで終了しました。iTester指標は添付ファイルにありすぐに使用できます。図17は、iHorizontalFormation(矢印)とiTesterをサブウィンドウで使用したチャートを示しています。緑色の線はエクイティを示し、赤色の線はバランスを示します。


図17 iHorizontalFormation(チャート上の矢印)に基づくiTester指標(サブウィンドウ内)


終わりに

本稿で説明したパターン検出法は初期タスクを解決し、フラグ、ペナント、三角形、ウェッジなどのさまざまな図形がチャート上に明瞭に示されます。考慮された方法は唯一の可能な方法でも絶対的に正しい方法でもありません。同じパターンを特定する他の方法も存在するでしょう。例えば、線形回帰を使用して、高値と低値を使って別々の計算を実行し、これらの線の勾配と収束/発散を確認することもできます。パターンを検出する共通の問題を構成する個々のサブタスクを処理すると、さらに多くのアイデアが可能になります。それにもかかわらず、本稿で指標を作成する際に考慮する価格分析手法は、他のテクニカル分析に関係した目的には役立つものです。 


添付ファイル

下記は本稿で作成したすべての指標です。

  • iHorizontalFormation
  • iFlag
  • iTester