ジグザグの力(第一部)指標基本クラスの開発

Anatoli Kazharski | 18 3月, 2019

内容


はじめに

以前の記事の1つでは、相対強度指数(RSI)のような指標をのどのように提示できるかを示しました。そのバージョンのうちの1つでは、得られた結果は、トレンドおよびフラット市場についてのシグナルを同時に受信するために使用することができます。この指標にはたぶん1つのことだけが欠けています。それは価格の振る舞いを定義する能力です。この能力はいつ取引するか、いつ取引を停止するかを決めるためにも非常に重要です。 

多くの研究者は、価格行動の決定を単に無視するか十分な注意を払っていません。同時に、機械学習やニューラルネットワークなどの複雑な方法が使用されます。その場合に生じる最も重要な質問は、特定のモデルを訓練するためにどのデータを供するべきかということです。この記事では、そのような研究のためのツールを拡張します。最適なパラメータを探す前に、取引に適した銘柄を選択する方法を見つけるでしょう。これを達成するために、ジグザグ指標の修正版とこのクラスに属する指標データの取得と操作を大幅に簡素化するコードクラスを使用します。

この一連の記事では、以下を実装します。


拡張版ジグザグ指標

通常、ジグザグタイプの指標はスプレッドを考慮せずにバーの高値と安値に基づいて構築されています。この記事では、修正版を紹介します。ここでは、より低いジグザグの極値のセグメントを作成するときにスプレッドを考慮します。取引は取引システムの価格チャネル内で実行されると仮定されています。売り気配値(ask)が買い気配値(bid)よりかなり高いことがよくあるので、これは重要です。たとえば、これは夜間に発生する可能性があります。したがって、買い気配値のみに基づいて指標を構築するのは間違っているでしょう。結局のところ、これらの価格で購入する可能性がないのであれば、バーの安値に基づいて指標の最下位点を構築することは意味がありません。もちろん、スプレッドは取引条件の中で考慮に入れることができますが、すべてがチャートにすぐに表示されているときはより良いです。最初はすべてがもっともらしいので、これは取引戦略の開発を単純化します。

さらに、ジグザグの極値が更新されたすべての点を確認することもできます。この場合、状況がさらに明確になります。それでは、指標コードについて考えてみましょう。基本的な機能と関数についてのみ説明します。

セグメントを構築するために2つの指標バッファが必要です。1つは高値(最大値)、もう1つは安値(最小値)です。それらはチャート上の単一行として表示されます。したがって、 6個の指標バッファが必要になり、そのうち5個が表示されます

指標バッファをすべてリストしましょう。

#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots   5
//---
#property indicator_color1  clrRed
#property indicator_color2  clrCornflowerBlue
#property indicator_color3  clrGold
#property indicator_color4  clrOrangeRed
#property indicator_color5  clrSkyBlue

//--- 指標バッファ
double low_ask_buffer[];    // 最小売り気配値
double high_bid_buffer[];   // 最大買い気配値
double zz_H_buffer[];       // 高値
double zz_L_buffer[];       // 安値
double total_zz_h_buffer[]; // すべての高値
double total_zz_l_buffer[]; // すべての安値

指標ラインを構築するために、外部パラメータにバー数(NumberOfBars)を設定する機能を追加しましょう。ゼロは、チャートに存在するすべてのデータが使用されることを意味します。MinImpulseSizeパラメータは、反対方向のセグメントの構築を開始するために価格が最後の極値から逸脱するポイント数を設定します。その上、追加のパラメータとして、どの指標バッファをチャートに表示するか、およびジグザグセグメントの色を定義する機能を追加しましょう。

//--- 外部パラメータ
input int   NumberOfBars   =0;       // バーの数
input int   MinImpulseSize =100;     // 線の最小点
input bool  ShowAskBid     =false;   // 気配値を表示する
input bool  ShowAllPoints  =false;   // すべての点を表示する
input color RayColor       =clrGold; // 線の色

グローバルレベルで、極値を計算するのに必要な補助変数を宣言します。以前に計算された極値点のインデックスを保存し、現在のセグメントの方向を追跡し、最小売り気配値と最大買い気配値を保存する必要があります。

//--- ZZ変数
int    last_zz_max  =0;
int    last_zz_min  =0;
int    direction_zz =0;
double min_low_ask  =0;
double max_high_bid =0;

FillAskBidBuffers()関数は最小売り気配値と最大買い気配値の指標バッファを埋めるために使用されています。スプレッドを考慮して、買い気配値バッファの場合、high配列の値を保存し、売り気配値バッファの場合は low配列の値を保存します

//+------------------------------------------------------------------+
//| 「High Bid」バッファと「Low Ask」バッファに書き入れる                 |
//+------------------------------------------------------------------+
void FillAskBidBuffers(const int i,const datetime &time[],const double &high[],const double &low[],const int &spread[])
  {
//--- 初めの日に届かない場合は終了する
   if(time[i]<first_date)
      return;
//---
   high_bid_buffer[i] =high[i];
   low_ask_buffer[i]  =low[i]+(spread[i]*_Point);
  }

FillIndicatorBuffers()関数はジグザグの極値を定義するために設定されています。計算は、MinImpulseSize外部パラメータに設定されたバー数に応じて、指定された日付からのみ実行されます。前の関数呼び出し中に定義されたセグメントの方向に応じて、プログラムは適切なコードブロックを入力します。

方向を定義するために、以下の条件がチェックされます。

FillIndicatorBuffers()関数の詳細は下で見られます。

//+------------------------------------------------------------------+
//| ZZ指標バッファに書き入れる                                          |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   if(time[i]<first_date)
      return;
//--- ZZが上昇した場合
   if(direction_zz>0)
     {
      //--- 新しい高値の場合
      if(high_bid_buffer[i]>=max_high_bid)
        {
         zz_H_buffer[last_zz_max] =0;
         last_zz_max              =i;
         max_high_bid             =high_bid_buffer[i];
         zz_H_buffer[i]           =high_bid_buffer[i];
         total_zz_h_buffer[i]     =high_bid_buffer[i];
        }
      //--- 方向が変更した場合(下向き)
      else
        {
         if(low_ask_buffer[i]<max_high_bid && 
            fabs(low_ask_buffer[i]-zz_H_buffer[last_zz_max])>MinImpulseSize*_Point)
           {
            last_zz_min          =i;
            direction_zz         =-1;
            min_low_ask          =low_ask_buffer[i];
            zz_L_buffer[i]       =low_ask_buffer[i];
            total_zz_l_buffer[i] =low_ask_buffer[i];
           }
        }
     }
//--- ZZが下降した場合
   else
     {
      //--- 新しい安値の場合
      if(low_ask_buffer[i]<=min_low_ask)
        {
         zz_L_buffer[last_zz_min] =0;
         last_zz_min              =i;
         min_low_ask              =low_ask_buffer[i];
         zz_L_buffer[i]           =low_ask_buffer[i];
         total_zz_l_buffer[i]     =low_ask_buffer[i];
        }
      //--- 方向が変更した場合(上向き)
      else
        {
         if(high_bid_buffer[i]>min_low_ask && 
            fabs(high_bid_buffer[i]-zz_L_buffer[last_zz_min])>MinImpulseSize*_Point)
           {
            last_zz_max          =i;
            direction_zz         =1;
            max_high_bid         =high_bid_buffer[i];
            zz_H_buffer[i]       =high_bid_buffer[i];
            total_zz_h_buffer[i] =high_bid_buffer[i];
           }
        }
     }
  }

指標のメイン関数のコードは、以下のリストに表示されています。指標は形成されたバーのみで計算されています。その後、(1)配列と変数をゼロに設定し、(2)計算のためのバー数と初期インデックスを定義します。最初は、指標バッファのすべての要素のデータが計算されますが、それ以降は、最後のバーのデータのみが計算されます。予備計算とチェックを実行した後、指標バッファが計算されていっぱいになります。

//+------------------------------------------------------------------+
//| カスタム指標反復関数                                                |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],
                const double &open[],const double &high[],const double &low[],const double &close[],
                const long &tick_volume[],const long &volume[],const int &spread[])
  {
//--- 各ティックでの計算を避ける
   if(prev_calculated==rates_total)
      return(rates_total);
//--- これが最初の計算の場合
   if(prev_calculated==0)
     {
      //--- 指標バッファをゼロに設定する
      ZeroIndicatorBuffers();
      //--- 変数をゼロに設定する
      ZeroIndicatorData();
      //--- 利用できるデータ量を確認する
      if(!CheckDataAvailable())
         return(0);
      //--- コピー用にさらにデータが指定されている場合は、現在の量が使用される
      DetermineNumberData();
      //--- 書く銘柄が始まるバーグラフを定義する 
      DetermineBeginForCalculate(rates_total);
     }
   else
     {
      //--- 最終値のみを計算する
      start=prev_calculated-1;
     }
//--- 「High Bid」および「Low Ask」指標バッファに書き入れる
   for(int i=start; i<rates_total; i++)
      FillAskBidBuffers(i,time,high,low,spread);
//--- 指標バッファにデータを書き入れる
   for(int i=start; i<rates_total-1; i++)
      FillIndicatorBuffers(i,time);
//--- データ配列サイズを返す
   return(rates_total);
  }

以下はEURUSD D1の指標です。

 図1 EURUSD D1での修正済みジグザグ指標

図1 EURUSD D1での修正済みジグザグ指標

次のスクリーンショットはEURMXN M5での指標です。ここでは、夜間にスプレッドが大幅に拡大するのを見ることができます。それにもかかわらず、指標はスプレッドを考慮して計算されます。

 図2 EURMXN M5での修正済みジグザグ指標

図2 EURMXN M5での修正済みジグザグ指標

次のセクションでは、現在の価格の振る舞いを定義するために必要なすべてのデータを取得するのに役立つメソッドを備えたコードクラスについて考察します。


ジグザグ指標データ分類クラス

価格は混乱して予測不能に変動します。価格が方向を変えることが多いフラットな動きは、ロールバックのない長い一方向トレンドに突然置き換えられる可能性があります。常に現在の状態を監視する必要がありますが、価格の振る舞いを正しく解釈するためのツールを用意することも重要です。これは、ジグザグデータを処理するために必要なすべてのメソッドを備えたCZigZagModuleコードクラスによって実現できます。その動作について説明します。

たとえば、異なる時間枠からのジグザグデータを使用して、複数のクラスインスタンスを同時に処理できるため、取得したセグメントを異なる色の傾向線を使用して視覚化する必要があるかもしれません。したがって、標準ライブラリのChartObjectsLines.mqhファイルをCZigZagModuleクラスのファイルに接続します。このファイルからは、トレンドライン使用のためにCChartObjectTrendクラスが必要です。トレンドラインの色はCZigZagModule::LinesColor()パブリックメソッドで指定できます。デフォルトは灰色(clrGray)に設定されています。

//+------------------------------------------------------------------+
//|                                                 ZigZagModule.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsLines.mqh>
//+------------------------------------------------------------------+
//| ジグザグ指標データ取得クラス                                         |
//+------------------------------------------------------------------+
class CZigZagModule
  {
protected:
   //--- セグメントの線
   CChartObjectTrend m_trend_lines[];

   //--- セグメントの線の色
   color             m_lines_color;
   //---
public:
                     CZigZagModule(void);
                    ~CZigZagModule(void);
   //---
public:
   //--- 線の色
   void              LinesColor(const color clr) { m_lines_color=clr; }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_lines_color(clrGray)
  {
// ...
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CZigZagModule::~CZigZagModule(void)
  {
  }

ジグザグ指標データを取得する前に、作業に必要な極値の数を設定する必要があります。これを実現するには、CZigZagModule::CopyExtremums()メソッドを呼び出す必要があります。個別の動的配列は、 (1)価格の極値、 (2)極値バーのインデックス、 (3)バーの時間、 (4)チャート上にトレンドラインを作成するためのセグメント数を格納するように宣言されています。配列のサイズも同じ方法で設定されます。

セグメント数は、指定された極値の数から自動的に計算されます。たとえば、CZigZagModule::CopyExtremums()メソッドに1を渡すと、高値と安値でデータを受け取ります。この場合、それはジグザグ指標の単一セグメントです。1より大きい値が渡された場合、セグメント数は常にコピーされた極値の数に2を引いた1に等しくなります。つまり、セグメント数は常に奇数になります。

class CZigZagModule
  {
protected:
   int               m_copy_extremums;    // 保存された高安値の数
   int               m_segments_total;    // セグメント数
   //--- 価格の極値
   double            m_zz_low[];
   double            m_zz_high[];
   //--- 極値バーのインデックス
   int               m_zz_low_bar[];
   int               m_zz_high_bar[];
   //--- 極値バーの時間
   datetime          m_zz_low_time[];
   datetime          m_zz_high_time[];
   //---
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_copy_extremums(1),
                                     m_segments_total(1)
  {
   CopyExtremums(m_copy_extremums);
  }

//+------------------------------------------------------------------+
//| 極値の数                                                          |
//+------------------------------------------------------------------+
void CZigZagModule::CopyExtremums(const int total)
  {
   if(total<1)
      return;
//---
   m_copy_extremums =total;
   m_segments_total =total*2-1;
//---
   ::ArrayResize(m_zz_low,total);
   ::ArrayResize(m_zz_high,total);
   ::ArrayResize(m_zz_low_bar,total);
   ::ArrayResize(m_zz_high_bar,total);
   ::ArrayResize(m_zz_low_time,total);
   ::ArrayResize(m_zz_high_time,total);
   ::ArrayResize(m_trend_lines,m_segments_total);
  }

ジグザグ指標データを使い始める前に、より便利に使用するためにそれらを上記のクラス配列に配置してください。極値カウンタとして使用する補助フィールドが必要になります。 

データを取得するには、CZigZagModule::GetZigZagData()メソッドが必要です。初期のジグザグ指標配列を時間配列と一緒に渡す必要があります。これらのソースデータは、CopyBuffer()およびCopyTime()関数を使用して取得できます。ソースデータから必要な値を取得する前に、すべてのフィールドと配列をリセットする必要があります。 次に、(1)価格の極値、 (2)極値バーのインデックス、 (3)バーの時間の指定された数を取得します。 

現在のセグメントの方向はメソッドの最後で定義されます。ここで、現在のセグメントの高値の時間が安値の時間を超えると、方向は上になります。そうでなければ、それは下向きです。

class CZigZagModule
  {
protected:
   int               m_direction;         // 方向
   int               m_counter_lows;      // 安値カウンタ
   int               m_counter_highs;     // 高値カウンタ
   //---
public:
   //--- データを取得する
   void              GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[]);
   //--- 構造体をリセットする
   void              ZeroZigZagData(void);
  };
//+------------------------------------------------------------------+
//| ジグザグデータを取得する                                            |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[])
  {
   int h_total =::ArraySize(zz_h);
   int l_total =::ArraySize(zz_l);
   int total   =h_total+l_total;
//--- ZZ変数をリセットする
   ZeroZigZagData();
//--- ループ内でコピーしたZZ値に沿って移動する
   for(int i=0; i<total; i++)
     {
      //--- 必要な数のZZの高/安値がすでに受信されている場合は、ループを終了する
      if(m_counter_highs==m_copy_extremums && m_counter_lows==m_copy_extremums)
         break;
      //--- 配列を超えた移動を管理する
      if(i>=h_total || i>=l_total)
         break;
      //--- 必要な量がコピーされるまで、高い値の配列を埋める
      if(zz_h[i]>0 && m_counter_highs<m_copy_extremums)
        {
         m_zz_high[m_counter_highs]      =zz_h[i];
         m_zz_high_bar[m_counter_highs]  =i;
         m_zz_high_time[m_counter_highs] =time[i];
         //---
         m_counter_highs++;
        }
      //--- 必要な量がコピーされるまで、安値の配列を埋める
      if(zz_l[i]>0 && m_counter_lows<m_copy_extremums)
        {
         m_zz_low[m_counter_lows]      =zz_l[i];
         m_zz_low_bar[m_counter_lows]  =i;
         m_zz_low_time[m_counter_lows] =time[i];
         //---
         m_counter_lows++;
        }
     }
//--- 価格変動の方向を定義する
   m_direction=(m_zz_high_time[0]>m_zz_low_time[0])?1 : -1;
  }

データが受信されたので、このクラスの他のメソッドを検討することができます。極値、極値バーインデックス、およびこれらの極値が形成されたバーの時間を取得するには、単に極値インデックスを指定して適切なメソッド(以下のコードリストを参照)を呼び出します。ここでは例として、 CZigZagModule::LowPrice()メソッドコードのみを示します。これらはすべてほぼ同じです。

class CZigZagModule
  {
public:
   //--- 指定された指数による極値の価格
   double            LowPrice(const int index);
   double            HighPrice(const int index);
   //--- 指定されたインデックスによる極値バーのインデックス
   int               LowBar(const int index);
   int               HighBar(const int index);
   //--- 指定されたインデックスによる極値バーの時間
   datetime          LowTime(const int index);
   datetime          HighTime(const int index);
  };
//+------------------------------------------------------------------+
//| 指定されたインデックスによる安値                                      |
//+------------------------------------------------------------------+
double CZigZagModule::LowPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_low))
      return(0.0);
//---
   return(m_zz_low[index]);
  }

セグメントサイズを取得する必要がある場合は、セグメントインデックスを唯一のパラメータとして指定して、CZigZagModule::SegmentSize()メソッドを呼び出します。指定されたインデックスが偶数か奇数かに応じて、セグメントサイズが計算される極値インデックスが適切に定義されます。インデックス値が偶数の場合、極値インデックスは互いに一致するため、セグメントの方向によっては計算する必要はありません

class CZigZagModule
  {
public:
   //--- 指定されたインデックスによるセグメントサイズ
   double            SegmentSize(const int index);
  };
//+------------------------------------------------------------------+
//| セグメントサイズをインデックスで返す                                  |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentSize(const int index)
  {
   if(index>=m_segments_total)
      return(-1);
//---
   double size=0;
//--- 値が偶数の場合
   if(index%2==0)
     {
      int i=index/2;
      size=::fabs(m_zz_high[i]-m_zz_low[i]);
     }
//--- 値が奇数の場合
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      size=::fabs(m_zz_high[h]-m_zz_low[l]);
     }
//---
   return(size);
  }

CZigZagModule::SegmentsSum()メソッドはセグメントの和を取得するのに使用されます。上記のCZigZagModule::SegmentSize()メソッドは、ループ内のすべてのセグメントに沿って移動するときに呼び出されるので、すべてが簡単です。

class CZigZagModule
  {
public:
   //--- すべてのセグメントの和
   double            SegmentsSum(void);
  };
//+------------------------------------------------------------------+
//| すべてのセグメントの総サイズ                                         |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentsSum(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_segments_total; i++)
      sum+=SegmentSize(i);
//---
   return(sum);
  }

その上、上方向または下方向のみに向けられたすべてのセグメントの合計を取得する必要があるかもしれません。例として、上向きセグメントのコードを以下に示します。それはすべて現在のセグメントの方向に依存します。上向きの場合は、現在のインデックスが計算のループで使用されます。現在の方向が下向きの場合、計算は最初のインデックスから開始して、高値のために1要素分オフセットします。すべてのセグメントの合計を下向きにする場合は、同じ方法を使用します。ただし、現在の方向が上向きの場合、オフセットは低域に対して実行されます。

class CZigZagModule
  {
public:
   //--- ((1)上向きおよび(2)下向きのセグメントの合計
   double            SumSegmentsUp(void);
   double            SumSegmentsDown(void);
  };
//+------------------------------------------------------------------+
//| すべての上向きセグメントのサイズを返す                                |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsUp(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()>0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i-1]-m_zz_low[i]);
        }
     }
//---
   return(sum);
  }

セット内のセグメントの総数に対する単方向セグメントの合計のパーセンテージ比率を取得すると便利な場合があります。これを実現するにはCZigZagModule::PercentSumSegmentsUp()およびCZigZagModule::PercentSumSegmentsDown()メソッドを使用します。これにより、これらの比率の差のパーセンテージ、つまりCZigZagModule::PercentSumSegmentsDifference()メソッドを取得することができます。これにより、現在の価格(トレンド)の方向がわかります。差がわずかであれば、価格は両方向で均等に変動します(フラット)。 

class CZigZagModule
  {
public:
   //--- セット内の全セグメントの合計数に対するセグメント合計の割合
   double            PercentSumSegmentsUp(void);
   double            PercentSumSegmentsDown(void);
   //--- セグメント間の差異の合計
   double            PercentSumSegmentsDifference(void);
  };
//+------------------------------------------------------------------+
//| すべての上向きセグメントの合計に対する割合を返す                        |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsUp(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsDown()/sum*100);
  }
//+------------------------------------------------------------------+
//| すべての下向きセグメントの合計に対する割合を返す                        |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDown(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsUp()/sum*100);
  }
//+------------------------------------------------------------------+
//| 全セグメントの合計の差をパーセンテージで返す                           |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDifference(void)
  {
   return(::fabs(PercentSumSegmentsUp()-PercentSumSegmentsDown()));
  }

価格の振る舞いを定義するために、別々のセグメントの期間と結果として得られるセット全体を取得するためのメソッドが必要です。CZigZagModule::SegmentBars()メソッドは、指定されたセグメントのバー数を取得するためのものです。メソッドのコードのロジックは、セグメントサイズを取得するためのCZigZagModule::SegmentSize()メソッドのロジックと同じです。したがって、ここでそのコードを提供しても意味がありません。 

取得したデータセットのバーの総数を取得するには、CZigZagModule::SegmentsTotalBars()メソッドを使用します。ここでは、セット内の最初と最後のバーのインデックスが定義され、その差が返されます。CZigZagModule::SegmentsTotalSeconds()メソッドも同じ原則に従います。唯一の違いは、セット内の秒数が返されることです。 

class CZigZagModule
  {
public:
   //--- 指定されたセグメントのバー数
   int               SegmentBars(const int index);
   //--- (1)バー数および(2)セグメントセットの秒数
   int               SegmentsTotalBars(void);
   long              SegmentsTotalSeconds(void);
  };
//+------------------------------------------------------------------+
//| すべてのセグメントのバー数                                           |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentsTotalBars(void)
  {
   int begin =0;
   int end   =0;
   int l     =m_copy_extremums-1;
//---
   begin =(m_zz_high_bar[l]>m_zz_low_bar[l])?m_zz_high_bar[l] : m_zz_low_bar[l];
   end   =(m_zz_high_bar[0]>m_zz_low_bar[0])?m_zz_low_bar[0] : m_zz_high_bar[0];
//---
   return(begin-end);
  }
//+------------------------------------------------------------------+
//| すべてのセグメントの秒数                                            |
//+------------------------------------------------------------------+
long CZigZagModule::SegmentsTotalSeconds(void)
  {
   datetime begin =NULL;
   datetime end   =NULL;
   int l=m_copy_extremums-1;
//---
   begin =(m_zz_high_time[l]<m_zz_low_time[l])?m_zz_high_time[l] : m_zz_low_time[l];
   end   =(m_zz_high_time[0]<m_zz_low_time[0])?m_zz_low_time[0] : m_zz_high_time[0];
//---
   return(long(end-begin));
  }

観察されたデータセット内の価格帯を見つける必要があるかもしれません。これらの目的のために、このクラスでは最小値と最大値、およびそれらの差(価格帯)を取得するためのメソッドを提供しています。

class CZigZagModule
  {
public:
   //--- セットの(1)最小値および(2)最大値
   double            LowMinimum(void);
   double            HighMaximum(void);
   //--- 価格帯
   double            PriceRange(void);
  };
//+------------------------------------------------------------------+
//| セットの最小値                                                     |
//+------------------------------------------------------------------+
double CZigZagModule::LowMinimum(void)
  {
   return(m_zz_low[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| セットの最大値                                                     |
//+------------------------------------------------------------------+
double CZigZagModule::HighMaximum(void)
  {
   return(m_zz_high[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| 価格帯                                                            |
//+------------------------------------------------------------------+
double CZigZagModule::PriceRange(void)
  {
   return(HighMaximum()-LowMinimum());
  }

CZigZagModuleクラスメソッドの後1つのセットにより、下記のような値を受信できます。

このクラスには、指定されたインデックスでセグメントのサイズとセグメントバーの数を取得するメソッドがすでにあります。したがって、上記のリストからメソッドのコードを理解するのは簡単でしょう。それらのすべてはそれらの中で呼び出されるメソッドだけが異なるので、CZigZagModule::SmallestSegmen()とCZigZagModule::MostNumberOfSegmentBars()2つだけのコードを提供します。

class CZigZagModule
  {
public:
   //--- セットの最小セグメント
   double            SmallestSegment(void);
   //--- セットの最大セグメント
   double            LargestSegment(void);
   //--- セットのセグメントバーの最小数
   int               LeastNumberOfSegmentBars(void);
   //--- セットのセグメントバーの最大数 
   int               MostNumberOfSegmentBars(void);
  };
//+------------------------------------------------------------------+
//| セットの最小セグメント                                              |
//+------------------------------------------------------------------+
double CZigZagModule::SmallestSegment(void)
  {
   double min_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      min_size=(size<min_size)?size : min_size;
     }
//---
   return(min_size);
  }
//+------------------------------------------------------------------+
//| セットのセグメントバーの最大数                                       |
//+------------------------------------------------------------------+
int CZigZagModule::MostNumberOfSegmentBars(void)
  {
   int max_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      max_bars=(bars>max_bars)?bars : max_bars;
     }
//---
   return(max_bars);
  }

パターンを検索するとき、指定されたセグメントのサイズが前のセグメントとどれほど異なるか(%単位)を定義する必要があるかもしれません。そのようなタスクを解決するにはCZigZagModule::PercentDeviation()メソッドを使用します。

class CZigZagModule
  {
public:
   //--- 偏差(パーセント)
   double            PercentDeviation(const int index);
  };
//+------------------------------------------------------------------+
//| 偏差(パーセント)                                                   |
//+------------------------------------------------------------------+
double CZigZagModule::PercentDeviation(const int index)
  {
   return(SegmentSize(index)/SegmentSize(index+1)*100);
  }

次に、カスタムプロジェクトで取得したデータを視覚化してCZigZagModuleクラスを使用する方法を見てみましょう。


取得されたデータセットの可視化

さまざまな時間枠からジグザグ指標ハンドルを受け取った後、EAが起動された現在のチャート上でセグメントを視覚化できます。可視化にトレンドラインタイプのグラフィックオブジェクトを使用しましょう。CZigZagModule::CreateSegment()プライベートメソッドは、オブジェクトを作成するために使用されます。ジグザグ指標データを異なるパラメータで異なる時間枠で表示する必要がある場合に、重複を避けるためにグラフィカルオブジェクトの一意の名前を形成するために使用されるセグメントインデックスとサフィックス(オプションパラメータ)を受け取ります。 

CZigZagModule::ShowSegments()およびCZigZagModule::DeleteSegments()パブリックメソッドを使用すると、グラフィックオブジェクトを表示および削除できます。

class CZigZagModule
  {
public:
   //--- オブジェクトを(1)表示および(2)削除する
   void              ShowSegments(const string suffix="");
   void              DeleteSegments(void);
   //---
private:
   //--- オブジェクトを作成する
   void              CreateSegment(const int segment_index,const string suffix="");
  };
//+------------------------------------------------------------------+
//| チャートでZZセグメントを表示する                                     |
//+------------------------------------------------------------------+
void CZigZagModule::ShowSegments(const string suffix="")
  {
   for(int i=0; i<m_segments_total; i++)
      CreateSegment(i,suffix);
  }
//+------------------------------------------------------------------+
//| セグメントを削除する                                                |
//+------------------------------------------------------------------+
void CZigZagModule::DeleteSegments(void)
  {
   for(int i=0; i<m_segments_total; i++)
     {
      string name="zz_"+string(::ChartID())+"_"+string(i);
      ::ObjectDelete(::ChartID(),name);
     }
  }

チャートにコメントを表示するためのメソッドがクラスに追加され、取得した指標データに関する基本的な情報を素早く取得できるようになりました。計算された指標データを簡単に示すメソッドのコードを以下に表示します。

 class CZigZagModule
  {
public:
   //--- チャートにコメントする
   void              CommentZigZagData();
   void              CommentShortZigZagData();
  };
//+------------------------------------------------------------------+
//| ジグザグデータをチャートコメントとして表示する                          |
//+------------------------------------------------------------------+
void CZigZagModule::CommentShortZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2)+
                  "\n---\n"+
                  "SmallestSegment(): "+::DoubleToString(SmallestSegment()/_Point,0)+"\n"+
                  "LargestSegment(): "+::DoubleToString(LargestSegment()/_Point,0)+"\n"+
                  "LeastNumberOfSegmentBars(): "+string(LeastNumberOfSegmentBars())+"\n"+
                  "MostNumberOfSegmentBars(): "+string(MostNumberOfSegmentBars());
//---
   ::Comment(comment);
  }

取得されたデータを受け取って視覚化するためのアプリケーションを開発しましょう。


取得された結果をテストするEA

ジグザグ指標データを受け取って視覚化するための簡単なテストEAを開発しましょう。コードを最大限に簡略化するために追加のチェックを実行することはしません。この例の主な目的は、データを取得すること自体の原則を説明することです。 

CZigZagModuleクラスを含むファイルをEAファイルに含め、そのインスタンスを宣言します。ここには2つの外部パラメータがあり、コピーする極値の数と新しいジグザグ指標セグメントを形成するための最小距離を指定できます。グローバルレベルでは、ソースデータを取得するための動的配列と指標ハンドルのための変数も宣言します。 

//+------------------------------------------------------------------+
//|                                                    TestZZ_01.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ZigZagModule.mqh>
CZigZagModule zz_current;

//--- 外部パラメータ
input int CopyExtremum   =3;
input int MinImpulseSize =0;

//--- 初期データの配列
double   l_zz[];
double   h_zz[];
datetime t_zz[];

//--- ZZ指標ハンドル
int zz_handle_current=WRONG_VALUE;

OnInit()関数では、(1)指標ハンドルを受け取り、(2)取得したセットから最終データを形成するための極値の数とセグメントの線の色を設定し、(3)ソースデータ配列の逆索引付け順序を設定します。

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- ZZ指標へのパス
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- 指標ハンドルを取得する
   zz_handle_current=::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,true,true);
//--- 取得するセグメントの色と極値の数を設定する
   zz_current.LinesColor(clrRed);
   zz_current.CopyExtremums(CopyExtremum);
//--- 逆索引の順序を設定する(... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

OnTick()関数では、そのハンドルとバーの開始時間によって指標ソースデータを取得します。その後、CZigZagModule::GetZigZagData()メソッドを呼び出して、最終的データを準備します。結論として、取得したジグザグ指標データのセグメントを視覚化し、そのデータをコメントとしてチャートに表示します。

//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- ソースデータを取得する
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
//--- 最終データを取得する
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
//--- チャートでセグメントを可視化する
   zz_current.ShowSegments();
//--- データの値をチャートでコメントとして表示する    
   zz_current.CommentZigZagData();
  }

ストラテジーテスターでEAをビジュアライゼーションモードで起動すると、次のようになります。その場合、5つの最大値と5つの最小値が得られました。その結果、チャート上で9つのセグメントが赤で強調表示されています。

 図3 可視化モードでのデモ(1つのジグザグ)

図3 可視化モードでのデモ(1つのジグザグ)

異なる時間枠から同時にジグザグ指標データを取得する必要がある場合は、テストEAのコードを少し強化する必要があります。3つの時間枠からデータを取得する必要がある場合の例を考えてみましょう。CZigZagModuleクラスの3つのインスタンスを宣言する必要があります。最初の時間枠は、EAが起動した現在のチャートから取得されます。他の2つを、たとえばM15とH1とします。

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_current;
CZigZagModule zz_m15;
CZigZagModule zz_h1;

各指標は、ハンドルを取得するための独自の変数を持ちます。

//--- ZZ指標ハンドル
int zz_handle_current =WRONG_VALUE;
int zz_handle_m15     =WRONG_VALUE;
int zz_handle_h1      =WRONG_VALUE;

次に、OnInit()関数で、指標ごとに別々にハンドルを受け取り、色と極値の数を設定します。

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- ZZ指標へのパス
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- 指標ハンドルを取得する
   zz_handle_current =::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_m15     =::iCustom(_Symbol,PERIOD_M15,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_h1      =::iCustom(_Symbol,PERIOD_H1,zz_path,10000,MinImpulseSize,false,false);
//--- セグメント色を設定する
   zz_current.LinesColor(clrRed);
   zz_m15.LinesColor(clrCornflowerBlue);
   zz_h1.LinesColor(clrGreen);
//--- 極値の数を設定する
   zz_current.CopyExtremums(CopyExtremum);
   zz_m15.CopyExtremums(CopyExtremum);
   zz_h1.CopyExtremums(CopyExtremum);
//--- 逆索引の順序を設定する(... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

データは上記のようにOnTick()関数で各ジグザグ指標インスタンスに対して個別に受信されます。チャートに表示できる指標は1つだけです。この場合、現在の時間枠の指標の簡単なデータを示します

//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
   zz_current.ShowSegments("_current");
   zz_current.CommentShortZigZagData();
//---
   ::CopyTime(_Symbol,PERIOD_M15,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_m15,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_m15,3,0,copy_total,l_zz);
   zz_m15.GetZigZagData(h_zz,l_zz,t_zz);
   zz_m15.ShowSegments("_m15");
//---
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,3,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
  }

それはこのように見えます。

 図4 可視化モードでのデモ(3つのジグザグ)

図4 可視化モードでのデモ(3つのジグザグ)

より長い時間枠からの指標の極値がわずかに左にシフトしていることがわかります。その理由は、トップとボトムはハンドルが受け取られた時間枠のバーの開始時間によって設定されるからです。 


CZigZagModuleクラスの開発の再開

すでに得られた結果を見ると、ジグザグ指標を使って作業を完了するのに十分であると思うかもしれません。しかし実際には、そうではありません。CZigZagModuleコードクラスの開発を継続する必要があります。 

これまでは、最新のバーから過去にさかのぼってジグザグ指標からデータを取得していましたが、特定の時間範囲でデータを取得する必要があるかもしれません。これを達成するために、異なるパラメータのセットを持つ別のメソッドCZigZagModule::GetZigZagData()を書きましょう。このバージョンでは、メソッド内で初期データを受け取るため、パラメータとして指標ハンドル、銘柄、時間枠、および時間範囲(開始日と終了日)が必要になります。

さらに、得られたデータの高値と安値の数を別々にカウントする必要があります。その場合、さらなる作業のための極値の数は、これらのカウンタ間の最小数によって定義されます。 

同じ名前のメソッドCZigZagModule::GetZigZagData()を別のパラメータセットと共に最後にここで呼び出します。ソースデータを持つ配列をパラメータとして渡して最終データを取得する方法について説明しながら、上記の設定について検討しました。

class CZigZagModule
  {
private:
   //--- ソースデータ取得のための配列
   double            m_zz_lows_temp[];
   double            m_zz_highs_temp[];
   datetime          m_zz_time_temp[];
   //---
public:
   //--- データを取得する
   void              GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time);
  };
//+------------------------------------------------------------------+
//| 渡されたハンドルからZZデータを取得する                                |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time)
  {
//--- ソースデータを取得する
   ::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
   ::CopyBuffer(handle,2,start_time,stop_time,m_zz_highs_temp);
   ::CopyBuffer(handle,3,start_time,stop_time,m_zz_lows_temp);
//--- カウンタ
   int lows_counter  =0;
   int highs_counter =0;
//--- 高値を数える
   int h_total=::ArraySize(m_zz_highs_temp);
   for(int i=0; i<h_total; i++)
     {
      if(m_zz_highs_temp[i]>0)
         highs_counter++;
     }
//--- 安値を数える
   int l_total=::ArraySize(m_zz_lows_temp);
   for(int i=0; i<l_total; i++)
     {
      if(m_zz_lows_temp[i]>0)
         lows_counter++;
     }
//--- 極値の数を設定する
   int copy_extremums=(int)::fmin((double)highs_counter,(double)lows_counter);
   CopyExtremums(copy_extremums);
//--- ループ内でコピーしたZZ値に沿って移動する
   GetZigZagData(m_zz_highs_temp,m_zz_lows_temp,m_zz_time_temp);
  }

取得したデータセット内の最小値と最大値の時間を取得するにはCZigZagModule::SmallestMinimumTime()およびCZigZagModule::LargestMaximumTime()メソッドを使用します。 

class CZigZagModule
  {
public:
   //--- 最小の最小時間
   datetime          SmallestMinimumTime(void);
   //--- 最大の最大時間
   datetime          LargestMaximumTime(void);
  };
//+------------------------------------------------------------------+
//| 最小の最小時間                                                     |
//+------------------------------------------------------------------+
datetime CZigZagModule::SmallestMinimumTime(void)
  {
   return(m_zz_low_time[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| 最大の最大時間                                                     |
//+------------------------------------------------------------------+
datetime CZigZagModule::LargestMaximumTime(void)
  {
   return(m_zz_high_time[::ArrayMaximum(m_zz_high)]);
  }

その他に、ジグザグセグメントを操作するためのメソッドのリストを広げましょう。一度にリンクによって渡される変数にいくつかの値を入れることは便利かもしれません。このクラスは、そのようなメソッドを3つ備えています。

同様の構造は、以前に考えられた他の方法にも存在するので、以下に1つのサンプルコードだけを提供します。 

class CZigZagModule
  {
public:
   //--- 指定されたセグメントの開始バーと終了バーを返す
   bool              SegmentBars(const int index,int &start_bar,int &stop_bar);
   //--- 指定されたセグメントの開始価格と終了価格を返す
   bool              SegmentPrices(const int index,double &start_price,double &stop_price);
   //--- 指定されたセグメントの開始時刻と終了時刻を返す
   bool              SegmentTimes(const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| 指定されたセグメントの開始バーと終了バーを返す                          |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentBars(const int index,int &start_bar,int &stop_bar)
  {
   if(index>=m_segments_total)
      return(false);
//--- 偶数の場合
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_bar =(Direction()>0)?m_zz_low_bar[i] : m_zz_high_bar[i];
      stop_bar  =(Direction()>0)?m_zz_high_bar[i] : m_zz_low_bar[i];
     }
//--- 奇数の場合
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_bar =m_zz_high_bar[h];
         stop_bar  =m_zz_low_bar[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_bar =m_zz_low_bar[l];
         stop_bar  =m_zz_high_bar[h];
        }
     }
//---
   return(true);
  }

M5チャートがあり、H1からデータを受信したとします。H1時間枠からパターンを探し、現在のH1時間枠から特定のジグザグセグメントの価格行動を定義する必要があります。言い換えれば、我々は特定のセグメントがどのようにより短い時間枠で形成されたかを知りたいのです。

前のセクションで示したように、より長い時間枠からのセグメントの極値は、より長い時間枠の開始時間によって現在のセグメントに表示されます。すでに、指定されたセグメントの開始時刻と終了時刻を返すCZigZagModule::SegmentTimes()メソッドがあります。より短い時間枠からジグザグデータを取得するためにこの時間範囲を使用すると、ほとんどの場合、より長い時間枠の他のセグメントに実際に属する多くの冗長なセグメントが得られます。さらに正確さが必要な場合に備えて、別のパラメータのセットを持つ、別のCZigZagModule::SegmentTimes()メソッドを書きましょう。さらに、(1) ソースデータと(2)渡された配列の最小値と最大値のインデックスを受け取るためのプライベートな補助メソッドがいくつか必要になります。 

class CZigZagModule
  {
private:
   //--- ソースデータを渡された配列にコピーする
   void              CopyData(const int handle,const int buffer_index,const string symbol,
                              const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                              double &zz_array[],datetime &time_array[]);
   //--- 渡された配列の(1)最小値と(2)最大値のインデックスを返す
   int               GetMinValueIndex(double &zz_lows[]);
   int               GetMaxValueIndex(double &zz_highs[]);
  };
//+------------------------------------------------------------------+
//| ソースデータを渡された配列にコピーする                                |
//+------------------------------------------------------------------+
void CZigZagModule::CopyData(const int handle,const int buffer_index,const string symbol,
                             const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                             double &zz_array[],datetime &time_array[])
  {
   ::CopyBuffer(handle,buffer_index,start_time,stop_time,zz_array);
   ::CopyTime(symbol,period,start_time,stop_time,time_array);
  }
//+------------------------------------------------------------------+
//| 渡された配列の最大値のインデックスを返す                               |
//+------------------------------------------------------------------+
int CZigZagModule::GetMaxValueIndex(double &zz_highs[])
  {
   int    max_index =0;
   double max_value =0;
   int total=::ArraySize(zz_highs);
   for(int i=0; i<total; i++)
     {
      if(zz_highs[i]>0)
        {
         if(zz_highs[i]>max_value)
           {
            max_index =i;
            max_value =zz_highs[i];
           }
        }
     }
//---
   return(max_index);
  }
//+------------------------------------------------------------------+
//| 渡された配列の最小値のインデックスを返す                               |
//+------------------------------------------------------------------+
int CZigZagModule::GetMinValueIndex(double &zz_lows[])
  {
   int    min_index =0;
   double min_value =INT_MAX;
   int total=::ArraySize(zz_lows);
   for(int i=0; i<total; i++)
     {
      if(zz_lows[i]>0)
        {
         if(zz_lows[i]<min_value)
           {
            min_index =i;
            min_value =zz_lows[i];
           }
        }
     }
//---
   return(min_index);
  }

あと1つのCZigZagModule::SegmentTimes()メソッドはより短い時間枠を考慮して指定されたセグメントの開始時間と終了時間を受け取るために実装されています。これにはいくつかの説明が必要です。メソッドには以下のパラメータが渡されます。

参照で渡される返されるパラメータ値:

まず、指定したセグメントの最初と最後のバーの開始時間を取得する必要があります。これには、上記の1番目のCZigZagModule::SegmentTimes() メソッドを呼び出します。 

次にCZigZagModule::CopyData()メソッドを使用して、極値とバーの時間のデータを受信します。セグメントの方向によっては、特定の順序でデータが取得されます。上方向の場合は、最初に短い時間枠のジグザグの最小値に関するデータを取得します。これは、長い時間枠の最初のバーのセグメントの一部を形成します。その後、より短い時間枠のジグザグの最大値のデータを取得します。これは、より長い時間枠の最後のバーのセグメントの一部を形成します。下方向の場合は、アクションの順序が逆になります。まず、最大値に関するデータとそれに続く最小値に関する情報を取得する必要があります。 

ソースデータを受け取ったら、最大値と最小値のインデックスを見つけます。これらの指標を使用すると、分析したセグメントの開始時間と終了時間をより短い時間枠で見つけることができます。

class CZigZagModule
  {
public:
   //--- 短めの時間枠を考慮して指定されたセグメントの開始時刻と終了時刻を返す
   bool              SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                  const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                  const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//|--- 指定されたセグメントの開始時刻と終了時刻を返す                       |
//| (短めの時間枠を考慮)                                                |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                 const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                 const int index,datetime &start_time,datetime &stop_time)
  {
//--- 現在の時間枠を考慮せずに時間を取得する
   datetime l_start_time =NULL;
   datetime l_stop_time  =NULL;
   if(!SegmentTimes(index,l_start_time,l_stop_time))
      return(false);
//---
   double   zz_lows[];
   double   zz_highs[];
   datetime zz_lows_time[];
   datetime zz_highs_time[];
   datetime start =NULL;
   datetime stop  =NULL;
   int      period_seconds=::PeriodSeconds(period);
//--- 上向きの場合にソースデータを取得する
   if(SegmentDirection(index)>0)
     {
      //--- 長めの時間枠の最初のバーのデータ
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
      //--- 長めの時間枠の最後のバーのデータ
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
     }
//--- 下向きの場合にソースデータを取得する
   else
     {
      //--- 長めの時間枠の最初のバーのデータ
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
      //--- 長めの時間枠の最後のバーのデータ
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
     }
//--- 最大値のインデックスを探す
   int max_index =GetMaxValueIndex(zz_highs);
//--- 最小値のインデックスを探す
   int min_index =GetMinValueIndex(zz_lows);
//--- セグメントの開始時と終了時を取得する
   start_time =(SegmentDirection(index)>0)?zz_lows_time[min_index] : zz_highs_time[max_index];
   stop_time  =(SegmentDirection(index)>0)?zz_highs_time[max_index] : zz_lows_time[min_index];
//--- 成功
   return(true);
  }

テスト用のEAを書きましょう。現在の時間枠はM5です。ストラテジーテスターの視覚化モードでEAを起動するために使用します。現在の時間枠からだけでなく、H1からもデータを受信します。EAコードは前に考えたものと似ているので、ここではOnTick()関数の内容だけを示します。

最初に、最初の方法を使用してH1のデータを取得し、わかりやすくするためにチャートにセグメントを表示します。次に、H1から3番目(インデックス2)のジグザグのセグメントの時間範囲で現在の時間枠(M5)からジグザグデータを取得します。これを行うには、現在の時間枠を考慮してセグメントの開始と終了を取得します

次に、2番目の方法で現在の時間枠のデータを取得して、すべてが順調であることを確認するためにチャートにセグメントを表示します。

//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- データを取得する1番目の方法
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   int      segment_index =2;
   int      start_bar     =0;
   int      stop_bar      =0;
   double   start_price   =0.0;
   double   stop_price    =0.0;
   datetime start_time    =NULL;
   datetime stop_time     =NULL;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//---
   zz_h1.SegmentBars(segment_index,start_bar,stop_bar);
   zz_h1.SegmentPrices(segment_index,start_price,stop_price);
   zz_h1.SegmentTimes(segment_index,start_time,stop_time);
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,segment_index,start_time_in,stop_time_in);
   
//--- データを取得する2番目の方法
   zz_current.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current.ShowSegments("_current");
   
//--- データをチャートコメントで表示する
   string comment="Current direction : "+string(zz_h1.Direction())+"\n"+
                  "\n---\n"+
                  "Direction > segment["+string(segment_index)+"]: "+string(zz_h1.SegmentDirection(segment_index))+
                  "\n---\n"+
                  "Start bar > segment["+string(segment_index)+"]: "+string(start_bar)+"\n"+
                  "Stop bar > segment["+string(segment_index)+"]: "+string(stop_bar)+
                  "\n---\n"+
                  "Start price > segment["+string(segment_index)+"]: "+::DoubleToString(start_price,_Digits)+"\n"+
                  "Stop price > segment["+string(segment_index)+"]: "+::DoubleToString(stop_price,_Digits)+
                  "\n---\n"+
                  "Start time > segment["+string(segment_index)+"]: "+::TimeToString(start_time,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time > segment["+string(segment_index)+"]: "+::TimeToString(stop_time,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Start time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(start_time_in,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(stop_time_in,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Extremums copy: "+string(zz_current.CopyExtremums())+"\n"+
                  "SmallestMinimumTime(): "+string(zz_current.SmallestMinimumTime())+"\n"+
                  "LargestMaximumTime(): "+string(zz_current.LargestMaximumTime());
//---
   ::Comment(comment);
  }

それはこのように見えます。

 図5 指定セグメント内のデータ受信

図5 指定セグメント内のデータ受信

次に、より長い時間枠の3つのセグメントからデータを受信するためのさらに別のEAを開発します。

ファイルの先頭に4つのCZigZagModuleクラスインスタンスを宣言する必要があります。そのうちの1つは、より長い時間枠(H1)用で、残りの3つは現在の時間枠用です。この場合、M5でテストを行います。 

CZigZagModule zz_h1;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

より明確にするために、より高いセグメント内のより短い時間枠のセグメントは異なる色で表示されます。

//--- セグメントの色を設定する
   zz_current0.LinesColor(clrRed);
   zz_current1.LinesColor(clrLimeGreen);
   zz_current2.LinesColor(clrMediumPurple);
   zz_h1.LinesColor(clrCornflowerBlue);

OnTick()関数では、初めにH1時間枠データを受け取った後で、最初のセグメント、2番目のセグメント、3番目のセグメントの下位の時間枠から順にデータを取得します。チャートのコメントに、より短い時間枠とより長い時間枠のデータの取得セグメントの各グループを別々に表示します。この場合、これはセグメント合計のパーセンテージ比率間の差です。これはCZigZagModule::PercentSumSegmentsDifference()メソッドを使用して取得できます。 

//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- データを取得する1番目の方法
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- 1番目のセグメントデータ
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current0.ShowSegments("_current0");
//--- 2番目のセグメントデータ
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current1.ShowSegments("_current1");
//--- 3番目のセグメントデータ
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current2.ShowSegments("_current2");
//--- データをチャートコメントで表示する
   string comment="H1: "+::DoubleToString(zz_h1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[0]: "+::DoubleToString(zz_current0.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[1]: "+::DoubleToString(zz_current1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[2]: "+::DoubleToString(zz_current2.PercentSumSegmentsDifference(),2);
//---
   ::Comment(comment);
  }

チャートでは次のように見えます。

 図6 指定した3つのセグメント内のデータを受信する

図6 指定した3つのセグメント内のデータを受信する

このアプローチは、パターン内の価格行動の性質を分析するための追加の機会を提供します。H1にパターンを定義し、価格が各セグメント内でどのように振舞うかを分析するとします。CZigZagModuleクラスメソッドを使用すると、極値およびセグメントのすべてのプロパティを取得できます。

この基本セットは、指標を構築するための複数のカスタムパラメータを開発するための出発点として使用できます。テストはそれからどんな利益が得られることができるかを示すでしょう。このWebサイトには、トピックに関する独自の調査を実施するのに役立つ可能性のある記事が多数含まれています。 


終わりに

ジグザグが取引シグナルを生成するのに適していないという考えは、取引フォーラムで広く普及しています。これは大きな誤解です。実際、価格動向の性質を判断するためにそれほど多くの情報を提供する指標は他にありません。これで、より詳細な分析に必要なすべてのジグザグ指標データを簡単に取得できるツールが手に入りました。

次回の連載記事では、 CZigZagModuleクラスを使用して開発できる指標、およびジグザグ指標からさまざまな銘柄に関する統計を取得したり、ジグザグに基づいた取引戦略を立てられることを確認するためのEAを紹介します。

ファイル名 コメント
MQL5\Indicators\Custom\ZigZag\ExactZZ_Plus.mq5 修正済みジグザグ指標
MQL5\Experts\ZigZag\TestZZ_01.mq5 単一データセットをテストするためのEA
MQL5\Experts\ZigZag\TestZZ_02.mq5 異なる時間枠の3つのデータセットをテストするためのEA
MQL5\Experts\ZigZag\TestZZ_03.mq5 指定されたより長い時間枠内のデータ取得をテストするためのEA
MQL5\Experts\ZigZag\TestZZ_04.mq5 3つの指定されたより長い時間枠セグメント内のデータ取得をテストするためのEA