ジグザグの力(第二部)データの受け取り、処理、表示の例

Anatoli Kazharski | 17 4月, 2019

内容


はじめに

本稿の最初の部分では、変更されたジグザグ指標と、そのタイプの指標のデータを受け取るためのクラスについて説明しました。ここでは、これらのツールに基づいて指標を開発する方法を示し、ジグザグ指標によって形成されたシグナルに従って取引を行うことを特徴とするテスト用のEAを作成します。

さらに、本稿ではグラフィカルユーザインタフェースを開発するためのEasyAndFastライブラリの新しいバージョンを紹介します。  

以下は本稿の主なトピックです。


価格行動を定義する指標

価格行動を定義する3つの指標を考察しましょう。 

これらの各指標のコード構造は、本稿の最初の部分で説明されているジグザグ指標と同じです。そのため、データが受信され、指標バッファに格納されるメイン関数(FillIndicatorBuffers)のみを説明します。


FrequencyChangeZZ指標

FrequencyChangeZZ 指標については、メイン関数のコードは以下に示されています。バーインデックスと時間配列が関数に渡されます。次に、現在のバーの時間から必要な数のジグザグ指標と時間配列要素(ソースデータ)をコピーします。ソースデータを受信した場合、最終データが要求されます。その後は、セグメントセットのバー数を返すメソッドを呼び出すだけです。結果は指標バッファの現在の要素に保存されます

//+------------------------------------------------------------------+
//| 指標バッファに書き入れる                                         |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         ::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         ::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- ZZデータを取得する
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- セグメントバッファのバー数を指標バッファに保存する
         segments_bars_total_buffer[i]=zz.SegmentsTotalBars();
         break;
        }
     }
  }

外部指標のパラメータで次を指定します。

(1)値はすべての利用可能なデータで計算する必要がある
(2) ジグザグ指標の新しいセグメント形成との最小偏差
(3) 最終データを得るための極値数

本稿のすべての指標は同じパラメータを持ちます。

 図1 指標外部パラメータ

図1 指標外部パラメータ

FrequencyChangeZZ指標は、下に表示されているようにサブウィンドウにチャートを表示します。より見やすくするために、ジグザグ指標がメインチャートにアップロードされます。その方向を選択する際に価格が低下したときに指標がはっきりと表示されます。  

 図2 FrequencyChangeZZ指標

図2 FrequencyChangeZZ指標


SumSegmentsZZ指標

SumSegmentsZZ指標では、データを取得するための主な関数は以下のようになります。すべてが以前の例と同じです。唯一の違いは、ここでは3つの指標バッファーが上向きセグメントと下向きセグメントに別々に書き入れられることです。現在の値に対するこれらのパラメータの平均を計算するために、もう1つのバッファが使用されています。

//+------------------------------------------------------------------+
//| 指標バッファに書き入れる                                         |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- ZZデータを取得する
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- データをセグメント別に取得する
         segments_up_total_buffer[i] =zz.SumSegmentsUp();
         segments_dw_total_buffer[i] =zz.SumSegmentsDown();
         segments_average_buffer[i]  =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/2;
         break;
        }
     }
  }

チャートにSumSegmentsZZをアップロードすると、以下のスクリーンショットのような結果が得られます。ここで、青い線が赤い線を超えた後は、上方向のセグメントの合計が下方向のセグメントの合計よりも大きいことがわかります。赤い線が青い線を超えると、状況は逆になります。ストラテジーテスターでの実験だけが、これが将来の価格方向に関する信頼できる情報源であるかどうかを教えてくれます。一見すると、一方向セグメントの合計が反対セグメントの合計を超えるほど、逆転確率は高くなります。 

 図3 SumSegmentsZZ指標

図3 SumSegmentsZZ指標


PercentageSegmentsZZ指標

ここでPercentageSegmentsZZ指標を見てみましょう。前の場合と同様に、3つの指標バッファが指標の主な関数で書き入れられるべきです。(1)上向きと(2)下向きのセグメント合計のパーセンテージ比率用にそれぞれ1つのバッファー、(3) これらの値の違いについての1つのバッファ。 

//+------------------------------------------------------------------+
//| 指標バッファに書き入れる                                           |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- ZZデータを取得する
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- セグメントのデータを取得する
         double sum_up =zz.SumSegmentsUp();
         double sum_dw =zz.SumSegmentsDown();
         double sum    =sum_up+sum_dw;
         //--- パーセント比と違い
         if(sum>0)
           {
            segments_up_total_buffer[i]   =zz.PercentSumSegmentsUp();
            segments_dw_total_buffer[i]   =zz.PercentSumSegmentsDown();
            segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
            break;
           }
        }
     }
  }

結果を以下に示します。解釈してみましょう。多方向セグメントの量のパーセンテージ比率の差が一定のしきい値より小さい場合、それはフラットと見なすことができます。この場合、価格は一方向に長い時間シフトする可能性があるため、比率は頻繁に入れ替える必要があることにも留意してください。差はオプティマイザによって選択されたレベルよりも低くなります。このような場合には、パターンの形成を一定の順序で考慮してモデルを適用する必要があります。

 図4 PercentageSegmentsZZ指標

図4 PercentageSegmentsZZ指標


MultiPercentageSegmentsZZ指標

前回の記事では、EAがより長い時間枠とより短い時間枠から同時にジグザグ指標データを分析することを実証しました。したがって、より長い期間のセグメント内で価格がどのように振舞うかをより詳細に分析することができました。言い換えれば、ここではより長い時間枠セグメントがより短い時間枠上にどのように形成されるかを定義しました。このパラメータのグループが、価格履歴にこれらの値を表示する個別の指標の形でどのようになるかを見てみましょう。

前の記事のEAと同様に、反対方向を向いたセグメントの合計のパーセンテージ比率間の差の4つの値を受け取ります。1つの値はより長い時間枠用で3つの値はより短い時間枠用です。値は、より長い時間枠で最後の3つのジグザグ指標セグメントによって計算されます。指標バッファの色は前の部分のEAと同じです。その後、指標をテストするためのEAを開発します。これによって、チャートでどのデータとどの期間を観察するかを理解しやすくなります。

//--- バッファの数
#property indicator_buffers 4
#property indicator_plots   4
//--- 色バッファの色
#property indicator_color1 clrSilver
#property indicator_color2 clrRed
#property indicator_color3 clrLimeGreen
#property indicator_color4 clrMediumPurple

CZigZagModuleクラスの4つのインスタンスを宣言します。

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

指標のより長い時間枠を外部パラメータで設定する能力を追加しましょう。

input             int NumberOfBars    =0;         // ジグザグ計算のバー数
input             int MinImpulseSize  =0;         // 線の最小点
input             int CopyExtremum    =5;         // 極値をコピーする
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // より長い時間枠

指標バッファに書き入れるための主な関数は次のように実装されています。まず、外部パラメータで指定されたより長い時間枠からソースデータを取得します。最終データを取得してパラメータ値を保存します。次に、3つの指標セグメントのデータをより長い時間枠から一貫して取得します。その後、すべての指標バッファに書き入れます。指標が履歴リアルタイム/テスターの最後のバーでで正しく計算されることができるように、2つの別々のコードブロックを開発しなければなりませんでした。 

//+------------------------------------------------------------------+
//| 指標バッファに書き入れる                                           |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const int total,const datetime &time[])
  {
   int index=total-i-1;
   int copy_total=1000;
   int h_buff=2,l_buff=3;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- より長い時間枠からソースデータを取得する
   datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
   CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp);
   CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp);
   CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
//--- より長い時間枠から最終データを取得する
   zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
   double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
//--- 1番目のセグメントデータ
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- 2番目のセグメントデータ
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- 3番目のセグメントデータ
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- 最後のバー
   if(i<total-1)
     {
      buffer_zz_higher_tf[i] =htf_value;
      buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
      buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
      buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
     }
//--- 履歴
   else
     {
      //--- より長い時間枠で新しいバーがある場合
      if(new_bar_time!=t_zz_buffer_temp[0])
        {
         new_bar_time=t_zz_buffer_temp[0];
         //---
         if(i>2)
           {
            int f=1,s=2;
            buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
            buffer_segment_0[i-f]    =buffer_segment_0[i-s];
            buffer_segment_1[i-f]    =buffer_segment_1[i-s];
            buffer_segment_2[i-f]    =buffer_segment_2[i-s];
           }
        }
      else
        {
         buffer_zz_higher_tf[i] =htf_value;
         buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
         buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
         buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
        }
     }
  }

MultiPercentageSegmentsZZ指標をテストするために、前の記事のEAをコピーして、数行を追加しましょう。より長い時間枠を設定するための外部パラメータを追加します。テスターのビジュアライゼーションモードでEAのテスト中に指標が表示されるようにするには、ハンドルを取得すれば十分です。 

//--- 外部パラメータ
input            uint CopyExtremum    =3;         // 極値をコピーする
input             int MinImpulseSize  =0;         // 最小インパルスサイズ
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // より長い時間枠

...

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {

...

//--- ZZ指標へのパス
   string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5";
   string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5";
//--- 指標ハンドルを取得する
   zz_handle_current   =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false);
   zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false);
   zz_handle           =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe);

...

   return(INIT_SUCCEEDED);
  }

テスターではこのように見えます。

 図5 MultiPercentageSegmentsZZ指標

図5 MultiPercentageSegmentsZZ指標

上記の指標はすべて、さまざまな組み合わせで、同時に異なる期間で使用することができます。記述されたツールを使用して銘柄のセットに関する統計を収集して、それらのうちどれが価格チャネルでの取引に適しているかを理解しましょう。


統計情報を収集して表示するEA

さらに、本稿ではグラフィカルユーザインタフェースを開発するためのEasyAndFastライブラリの新しいバージョンを紹介します。ここでライブラリの新機能のみを一覧表示します。

 図6 要素のグループ化

図6 要素のグループ化

ライブラリの新バージョンはコードベースでダウンロードできます。 

次に、新しいバージョンのEasyAndFastライブラリを使用して統計を収集するためのテスト用EAを作成しましょう。アプリケーションのグラフィカルユーザーインターフェイス(GUI)の開発から始めて、統計を収集し表示するメソッドに進みます。

必要なGUIコントロールを定義しましょう。

前述したように、GUIをより速くより便利に開発するには、CWndCreateクラスを基本クラスとして含める必要があります。完全な接続はCWndContainer -> CWndEvents -> CWndCreate -> CProgramとなります。CWndCreateクラスが存在することで、カスタムクラスで個別のメソッドを作成することなく、1行でGUI要素を作成できます。このクラスには、ほとんどすべてのライブラリ要素用のさまざまなテンプレートが含まれています。必要に応じて新しいテンプレートを追加することもできます。 

GUIを作成するには、次のコードに示すように上記のリストに含まれる要素を宣言します。現在のバージョンのCWndCreateクラスには、高速で表を作成できるテンプレートはないため、このメソッドを独自に開発しましょう。 

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndCreate.mqh>
//+------------------------------------------------------------------+
//| アプリケーション開発クラス                                          |
//+------------------------------------------------------------------+
class CProgram : public CWndCreate
  {
private:
   //--- ウィンドウ
   CWindow           m_window;
   //--- ステータスバー
   CStatusBar        m_status_bar;
   //--- ドロップダウンカレンダー
   CDropCalendar     m_from_date;
   CDropCalendar     m_to_date;
   //--- ボタン
   CButton           m_request;
   //--- 入力フィールド
   CTextEdit         m_filter;
   CTextEdit         m_level;
   //--- コンボボックス
   CComboBox         m_data_type;
   //--- 表
   CTable            m_table;
   //--- プログレスバー
   CProgressBar      m_progress_bar;
   //---
public:
   //--- GUIを作成する
   bool              CreateGUI(void);
   //---
private:
   //--- 表
   bool              CreateTable(const int x_gap,const int y_gap);
  };

そのようなコンテンツでグラフィカルインタフェースを作成するには、以下のコードに示すように、プロパティの値を引数として指定して CWndCreateクラスの必要なメソッドを呼び出すだけです。メソッドパラメータが関連付けられているプロパティを定義するには、その中にテキストカーソルを置いてCtrl + Shift + Spaceをクリックします。

 図7 メソッドパラメータの表示

図7 メソッドパラメータの表示

追加のプロパティを設定する必要がある場合は、通貨フィルタ入力フィールドに関する例に示されているのと同じ方法で設定できます。ここでは、要素を作成した直後にチェックボックスをデフォルトで有効にすることを示しています。

//+------------------------------------------------------------------+
//| GUIを作成する                                                    |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- コントロールフォームを作成する
   if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",1,1,640,480,true,true,true,true))
      return(false);
//--- ステータスバー
   string text_items[1];
   text_items[0]="For Help, press F1";
   int width_items[]={0};
   if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items))
      return(false);
//--- 通貨フィルタ入力フィールド
   if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,0,true,7,25,627,535,"USD","Example: EURUSD,GBP,NOK"))
      return(false);
   else
      m_filter.IsPressed(true);
//--- ドロップダウンカレンダー
   if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,0,7,50,130,D'2018.01.01'))
      return(false);
   if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,0,150,50,117,::TimeCurrent()))
      return(false);
//--- レベルを設定するための入力フィールド
   if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,0,false,280,50,85,50,100,0,1,0,30))
      return(false);
//--- ボタン
   if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70))
      return(false);
//--- 表
   if(!CreateTable(2,75))
      return(false);
//--- プログレスバー
   if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3))
      return(false);
//--- GUI開発を終了する
   CWndEvents::CompletedGUI();
   return(true);
  }

表の場合は、要素を作成する前に指定する必要がある多数のプロパティを持つ複雑な要素であるため、カスタムメソッドを作成します。表は4つのコラムを持ちます。1番目のコラムには通貨ペアが表示されます。残り3つにはM5、H1H8の3つの時間枠の統計データが表示されます。

//+------------------------------------------------------------------+
//| 表を作成する                                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 4
#define ROWS1_TOTAL    1
//--- メイン要素へのポインタを保存する
   m_table.MainPointer(m_window);
//--- コラム幅の配列
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,50);
   width[0]=80;
//--- X軸方向にオフセットしたテキストの配列
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,7);
//--- 列内のテキスト配置の配列
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_LEFT;
//--- プロパティ
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ShowHeaders(true);
   m_table.IsSortMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoYResizeMode(true);
   m_table.AutoXResizeRightOffset(2);
   m_table.AutoYResizeBottomOffset(24);
//--- コントロールを作成する
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- ヘッダ
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
//--- オブジェクトグループの共通配列にオブジェクトを追加する
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

ここでデータを取得するメソッドを考察しましょう。まず、作業する銘柄を取得する必要があります。このEAバージョンでは、外貨銘柄からデータを受け取ります。同時に、取引が無効になっている銘柄を除外します。ここでは、フィルタによって銘柄を確認するためのCheckFilterText()補助メソッドも必要となります。入力フィールドでは、ユーザーは銘柄名に含まれるべきコンマ区切りのテキスト値を入力することができます。フィールドのチェックボックスがオフになっているまたはテキストが入力されていない場合、チェックは実行されません。チェックに合格して一致が見つかると、入力されたテキストは部分文字列に分割され、必要な文字列の検索が実行されます。 

class CProgram : public CWndCreate
  {
private:
   //--- フィルタによって銘柄を確認する
   bool              CheckFilterText(const string symbol_name);
  };
//+------------------------------------------------------------------+
//| フィルタによって銘柄を確認する                                      |//+------------------------------------------------------------------+
bool CProgram::CheckFilterText(const string symbol_name)
  {
   bool check=false;
//--- 銘柄名のフィルタがオンの場合
   if(!m_filter.IsPressed())
      return(true);
//--- テキストが入力された場合
   string text=m_filter.GetValue();
   if(text=="")
      return(true);
//--- 部分文字列に分ける
   string elements[];
   ushort sep=::StringGetCharacter(",",0);
   ::StringSplit(text,sep,elements);
//--- 一致を確認する
   int elements_total=::ArraySize(elements);
   for(int e=0; e<elements_total; e++)
     {
      //--- 外側のスペースを削除する
      ::StringTrimLeft(elements[e]);
      ::StringTrimRight(elements[e]);
      //--- 一致が検知された場合
      if(::StringFind(symbol_name,elements[e])>-1)
        {
         check=true;
         break;
        }
     }
//--- 結果
   return(check);
  }

CProgram::GetSymbols()メソッドでは、サーバー上に存在するすべての銘柄を反復して渡し、指定された基準に合うものを配列にまとめます一般的なループでは、すべての銘柄は[気配値表示]ウィンドウから削除されます。その後、配列に含まれるものだけがウィンドウに追加されます。 

class CProgram : public CWndCreate
  {
private:
   //--- 銘柄の配列
   string            m_symbols[];
   //---
private:
   //--- 銘柄を取得する
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
//| 銘柄を取得する                                                   |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
//--- プログレス
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(0,1);
//--- 銘柄配列をクリアする
   ::ArrayFree(m_symbols);
//--- 外貨銘柄の配列を集める
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- 銘柄名を取得する
      string symbol_name=::SymbolName(i,false);
      //--- [気配値情報]ウィンドウで非表示にする
      ::SymbolSelect(symbol_name,false);
      //--- 外貨銘柄でない場合は、次に移る
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- 取引が無効の場合は、次に移る
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED)
         continue;
      //--- フィルタによって銘柄を確認する
      if(!CheckFilterText(symbol_name))
         continue;
      //--- 銘柄を配列に保存する
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1,1000);
      m_symbols[array_size]=symbol_name;
     }
//--- 配列が空の場合は、現在の銘柄をデフォルトの銘柄として設定する
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=_Symbol;
     }
//--- [気配値情報]ウィンドウで表示する
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

収集された銘柄に関するデータを取得するには、まずそれらの指標ハンドルを取得する必要があります。指標ハンドルを取得するたびに、計算が終了するまでさらなる分析のためにデータをコピーするのを待つ必要があります。すべてのデータが受信された後、必要な計算が実行されます。 

CProgram::GetSymbolsData()メソッドはこのために使用されます。パラメータは銘柄と時間枠の2つです。指標ハンドルを受け取った後、指定された時間範囲に何本のバーが存在するかを調べます。日付範囲はアプリケーションのGUIコントロールを使用して指定できます。次に、計算された指標データの量を取得します。指標計算はハンドルを受け取った直後に完了しない場合があります。そのため、BarsCalculated()関数が-1を返した場合、指定された時間範囲内のバー数に等しいかそれを超えるまで、有効な値の取得を試みます

指標データが計算されたら、配列への格納を試みることができます。値がバーの総数以上になるまでに、いくつかの試行が必要な場合もあります。 

指標が配列に正常にコピーされた場合、後は必要な計算するだけです。この場合、指標値が指定されたレベルを超えているデータ量の総量に対するパーセンテージの比率を計算します。このレベルはアプリケーションのGUIでも指定できます。 

メソッドの最後には、計算ハンドルを解放して指標ハンドルを削除します。CProgram::GetSymbolsData()メソッドは、選択した銘柄のリストと複数の時間枠に対して複数回呼び出されます。それぞれの計算は一度だけ実行されるべきであり、結果として得られる値はGUI表に表示されるので、ハンドルは不必要になるため削除することができます。 

class CProgram : public CWndCreate
  {
private:
   //--- Get symbol data
   double            GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| 銘柄データを取得する                                             |
//+------------------------------------------------------------------+
double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period)
  {
   double result       =0.0;
   int    buffer_index =2;
//--- 指標ハンドルを取得する
   string path   ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5";
   int    handle =::iCustom(symbol,period,path,0,0,5);
   if(handle!=INVALID_HANDLE)
     {
      //--- 指定された期間内のデータをコピーする
      double   data[];
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      //--- 指定された期間のバー数
      int bars_total=::Bars(symbol,period,start_time,end_time);
      //--- 指定された期間のバー数
      int bars_calculated=::BarsCalculated(handle);
      if(bars_calculated<bars_total)
        {
         while(true)
           {
            ::Sleep(100);
            bars_calculated=::BarsCalculated(handle);
            if(bars_calculated>=bars_total)
               break;
           }
        }
      //--- データを取得する
      int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
      if(copied<1)
        {
         while(true)
           {
            ::Sleep(100);
            copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
            if(copied>=bars_total)
               break;
           }

        }
      //--- データが受け取られていない場合は終了する
      int total=::ArraySize(data);
      if(total<1)
         return(result);
      //--- 繰り返し数を数える
      int counter=0;
      for(int k=0; k<total; k++)
        {
         if(data[k]>(double)m_level.GetValue())
            counter++;
        }
      //--- パーセント比
      result=((double)counter/(double)total)*100;
     }
//--- 指標を解放する
   ::IndicatorRelease(handle);
//--- 値を返す
   return(result);
  }

新しい銘柄のリストが形成されるたびに、表の再構築が必要です。これを行うには、単にすべての行を削除して必要な量を追加するだけです。

class CProgram : public CWndCreate
  {
private:
   //--- 表を再構築する
   void              RebuildingTables(void);
  };
//+------------------------------------------------------------------+
//| 表を再構築する                                                |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- すべての行を削除する
   m_table.DeleteAllRows();
//--- データを追加する
   int symbols_total=::ArraySize(m_symbols);
   for(int i=1; i<symbols_total; i++)
      m_table.AddRow(i);
  }

表の行にデータを書き入れるにはCProgram::SetData()メソッドが使用されます。2つのパラメータ(列インデックスと時間枠)が渡されます。ここでは、指定された列のセルを移動し、ループ内で計算された値を書き入れますプログレスバーには、データが受信されたばかりの銘柄と時間枠が表示されます。ユーザーは現在何が起こっているのかを理解できます。

class CProgram : public CWndCreate
  {
private:
   //--- 値を指定された列に設定する
   void              SetData(const int column_index,const ENUM_TIMEFRAMES period);
   //--- 時間枠から文字列へ
   string            GetPeriodName(const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| 値を指定された列に設定する                                          |
//+------------------------------------------------------------------+
void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period)
  {
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
     {
      double value=GetSymbolsData(m_symbols[r],period);
      m_table.SetValue(column_index,r,string(value),2,true);
      m_table.Update();
      //--- プログレス
      m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]...");
      m_progress_bar.Update(r,m_table.RowsTotal());
     }
  }
//+------------------------------------------------------------------+ 
//| 期間の文字列値を返す                                             |
//+------------------------------------------------------------------+ 
string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period)
  {
   return(::StringSubstr(::EnumToString(period),7));
  }

データを表に書き入れるメインメソッドはCProgram::SetDataToTable()です。表はまずここで再構築されます。次に、ヘッダとデータ型を設定する必要があります(TYPE_DOUBLE)。収集した銘柄を最初の列に設定します。表を再描画して変更をすぐに確認します

これで、指定したすべての銘柄と時間枠について指標データの受信を開始できます。これを行うには、単にCProgram::SetData()メソッドを呼び出して、列インデックスと時間枠をパラメータとして渡します。 

class CProgram : public CWndCreate
  {
private:
   //--- 表にデータを書き入れる
   void              SetDataToTable(void);
  };
//+------------------------------------------------------------------+
//| 表にデータを書き入れる                                            |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable(void)
  {
//--- プログレス
   m_progress_bar.LabelText("Data preparation...");
   m_progress_bar.Update(0,1);
//--- 表を再構築する
   RebuildingTable();
//--- ヘッダ
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
   for(uint i=1; i<m_table.ColumnsTotal(); i++)
      m_table.DataType(i,TYPE_DOUBLE);
//--- 値を1番目の列に設定する
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
      m_table.SetValue(0,r,m_symbols[r],0,true);
//--- 表を表示する
   m_table.Update(true);
//--- 残りの列にデータを書き入れる
   SetData(1,PERIOD_M5);
   SetData(2,PERIOD_H1);
   SetData(3,PERIOD_H8);
  }

CProgram::GetData()メソッドを使用して新しいデータを受信する前に、CProgram::StartProgress()メソッドを補助的に使用してプログレスバーを表示します。新しいデータが受信されたら、プログレスバーを非表示にして押下されたボタンからフォーカスを外します。これにはCProgram::EndProgress()メソッドを呼び出します。 

class CProgram : public CWndCreate
  {
private:
   //--- データを取得する
   void              GetData(void);

   //--- プログレス(1)開始と(2)終了
   void              StartProgress(void);
   void              EndProgress(void);
  };
//+------------------------------------------------------------------+
//| データを取得する                                                  |
//+------------------------------------------------------------------+
void CProgram::GetData(void)
  {
//--- プログレス開始
   StartProgress();
//--- 銘柄リストを取得する
   GetSymbols();
//--- 表にデータを書き入れる
   SetDataToTable();
//--- プログレス終了
   EndProgress();
  }
//+------------------------------------------------------------------+
//| プログレス開始                                                   |
//+------------------------------------------------------------------+
void CProgram::StartProgress(void)
  {
   m_progress_bar.LabelText("Please wait...");
   m_progress_bar.Update(0,1);
   m_progress_bar.Show();
   m_chart.Redraw();
  }
//+------------------------------------------------------------------+
//| プログレス終了                                                     |
//+------------------------------------------------------------------+
void CProgram::EndProgress(void)
  {
//--- プログレスバーを非表示にする
   m_progress_bar.Hide();
//--- ボタンを更新する
   m_request.MouseFocus(false);
   m_request.Update(true);
   m_chart.Redraw();
  }

ユーザが[Request]をクリックすると、ON_CLICK_BUTTONカスタムイベントが生成され、押下されたボタンは要素IDで特定できます。[Request]ボタンが押下された場合は、データ取得プロセスを開始します。 

表作成メソッドには、ヘッダをクリックして表をソートする機能を追加しました。そのたびにON_SORT_DATAカスタムイベントが生成されます。イベントを受信したら、表を更新して変更を表示します。 

//+------------------------------------------------------------------+
//| イベントハンドラ                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタン押下イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      if(lparam==m_request.Id())
        {
         //--- データを取得する
         GetData();
         return;
        }
      //---
      return;
     }
//--- 表のソートイベント
   if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA)
     {
      if(lparam==m_table.Id())
        {
         m_table.Update(true);
         return;
        }
      //---
      return;
     }
  }

ここで結果を見てみましょう。プログラムをコンパイルしてチャートに読み込むと、結果は以下のスクリーンショットのようになります。次のパラメータがデフォルトで設定されています。

 図8 MQLアプリケーションのGUI

図8 MQLアプリケーションのGUI

Requestを押下するとデータ取得が開始されます。

 図9 データの受信

図9 データの受信

データが受信されたら、並び替えできます。

 図10 表のデータの並び替え

図10 表のデータの並び替え

このアプリケーションを変更して使用することで、自分のタスクのいくつかを解決することができます。表には他のパラメータを書き入れることもできます。

以下に、表データの可視性をさらに向上させる方法を示す別の例を示します。このセクションの冒頭ですでに説明したように、 EasyAndFast ライブラリの最新バージョンには、表のセルの背景色を設定する機能があります。これにより、さまざまな表エディタで行われているのと同じ方法で表を形成することができます。以下のスクリーンショットは、Excelスプレッドシートのフォーマットデータを示しています。各セルの背景色は、配列を並べ替えても同じままです。

 図11 Excelでのカラースケール

図11 Excelでのカラースケール

このような設定により、視覚的なデータ分析を迅速に実行することができます。 

上記で検討したMQLアプリケーションに小さな変更と追加を加えましょう。各表のセルに一意の色を設定するには、ゼブラスタイルの書式設定を無効にします。このコード文字列をコメントアウトしてください。

// m_table.IsZebraFormatRows(clrWhiteSmoke);

次に、表のフォーマットを設定するCProgram::SetColorsToTable()メソッドを作成しましょう。色と作業するにはCColorsクラスが使用されます。これはGUIを作成するためのライブラリにすでに存在しているので、ファイルをプロジェクトに含める必要はありません。作業用に、(1)グラデーションの色を取得するための配列、および、(2)グラデーションを作成する色の配列の2つの配列を宣言します。ここでは3色のグラデーションを作成します。値が小さいほど、色が赤くなります(clrTomato)。値が大きいほど、青になります(clrCornflowerBlue)。これら2つのカラーゾーンを分けるために白を追加しましょう。 

値のサイズの範囲を最小値から最大値まで定義します。これはグラデーション配列のサイズになります。配列サイズを設定して配列に書き入れるためにはCColors::Gradient()メソッドを使用します。表のセルの色は最後のループで設定されます。配列範囲から外れないようにするために、インデックスはセル値から範囲の最小値を引いた値として計算されます。メソッドの終わりでは、実装された変更を表示するために表が更新されます。

class CProgram : public CWndCreate
  {
private:
   //--- 表にセルの背景色を書き入れる
   void              SetColorsToTable(void);
  };
//+------------------------------------------------------------------+
//| 表の形式を設定する                                              |
//+------------------------------------------------------------------+
void CProgram::SetColorsToTable(void)
  {
//--- 色の操作
   CColors clr;
//--- グラデーションを受信するための配列
   color out_colors[];
//--- 3色グラデーション
   color colors[3]={clrTomato,clrWhite,clrCornflowerBlue};
//--- 表の極値を見つける
   double max =0;
   double min =100;
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         max =::fmax(max,(double)m_table.GetValue(c,r));
         min =::fmin(min,(double)m_table.GetValue(c,r));
        }
     }
//--- 四捨五入して整数にする
   max =::floor(max);
   min =::floor(min);
//--- 範囲を取得する
   int range =int(max-min)+1;
//--- 色のグラデーション配列を取得する
   clr.Gradient(colors,out_colors,range);
//--- セルの背景色を設定する
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         int index=(int)m_table.GetValue(c,r)-(int)min;
         m_table.BackColor(c,r,out_colors[index],true);
        }
     }
//--- 表を更新する
   m_table.Update();
  }

GUIでは以下のように見えます。この場合、結果は、値が小さいほど、対象領域のトレンドの数が少ないことを示しています。できるだけ多くのデータを使用して情報を取得するには、できるだけ広い範囲の日付を設定することが賢明です。 

 図12 テーブルデータを視覚化するためのカラースケール

図12 テーブルデータを視覚化するためのカラースケール

日付の範囲が広いほど、使用されるデータが多くなるため、データの生成とパラメータの計算にかかる時間が長くなります。十分なデータがない場合は、サーバーからのダウンロードを試みます。 


セグメント数をサイズ別に数える

ここで、セグメントの数をサイズで数えるプログラムを開発しましょう。前のセクションからEAをコピーし、それに必要な変更と追加を行います。表は2つあります。1つ目は、分析された銘柄のリストを持つ列を1つだけ使用します。2番目は2つのデータ列を使用します。(1)ポイントの範囲を広げ、(2)最初の列の範囲でセグメントの数を増やします。アプリケーションがチャートにアップロードされた直後にGUIは以下のように見えます。

 図13 セグメント数をサイズ別に数えるプログラム

図13 セグメント数をサイズ別に数えるプログラム

Requestボタンで指定されたフィルタを使用した銘柄リストをリクエストします。Calculateをクリックすると、指定された時間範囲のデータが収集され、2番目の表に配信されます。 

基本的にすべてのメソッドは前のEAと同じままであるため、2番目の表に関連するものだけを考えてみましょう。まず、指標データを受け取る必要があります。これはCProgram::GetIndicatorData()メソッドで行われます。まずジグザグ指標に接続してから、指定された時間範囲内のデータを取得します。銘柄、時間枠、および取得した指標セグメントの数がステータスバーに表示されます。 

class CProgram : public CWndCreate
  {
private:
   //--- 指標データを取得する
   void              GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| 指標データを取得する                                             |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period)
  {
//--- 指標ハンドルを取得する
   string path   ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5";
   int    handle =::iCustom(symbol,period,path,0,0);
   if(handle!=INVALID_HANDLE)
     {
      //--- 指定された期間のデータをコピーする
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time);
      //--- データをステータスバーで表示する
      string text="["+symbol+","+(string)GetPeriodName(period)+"] - Segments total: "+(string)m_zz.SegmentsTotal();
      m_status_bar.SetValue(0,text);
      m_status_bar.GetItemPointer(0).Update(true);
     }
//--- 指標を解放する
   ::IndicatorRelease(handle);
  }

最初の列のステップを指定した価格帯を計算する必要があります。これにはCProgram::GetLevels()メソッドを使用します。範囲数を定義するには、まず取得したデータセットの最大セグメントサイズを取得します。次に、指定されたステップを使用して、最大値に達するまでループで配列にレベルを書き入れます。 

class CProgram : public CWndCreate
  {
private:
   //--- 範囲の配列
   int               m_levels_array[];
   //---
private:
   //--- レベルを取得する
   void              GetLevels(void);
  };
//+------------------------------------------------------------------+
//| レベルを取得する                                                  |
//+------------------------------------------------------------------+
void CProgram::GetLevels(void)
  {
//--- 配列を解放する
   ::ArrayFree(m_levels_array);
//--- 最大セグメントサイズを取得する
   int max_value=int(m_zz.LargestSegment()/m_symbol.Point());
//--- 配列にレベルを書き入れる
   int counter_levels=0;
   while(true)
     {
      int size=::ArraySize(m_levels_array);
      ::ArrayResize(m_levels_array,size+1);
      m_levels_array[size]=counter_levels;
      //---
      if(counter_levels>max_value)
         break;
      //---
      counter_levels+=(int)m_step.GetValue();
     }
  }

2番目の表にデータを書き入れるにはCProgram::SetDataToTable2()メソッドを使用します。まず、銘柄が1番目の表のリストで強調表示されているかどうかが確認されます。そうでない場合、プログラムはメッセージをエキスパートログに送信してメソッドを終了します。1番目の表の行が強調表示されている場合は、銘柄を定義してそのデータを取得します。その後、指標データを受け取ってレベルを計算するために上記のメソッドが呼ばれます。EAが立ち上げられたのと同じ時間枠の指標データを受け取ります。

レベルの数がわかれば、必要なサイズの表を作成して値を書き入れることができます。まず、最初の列に範囲値を書き入れます。その後、2列目に書き入れます。すべての範囲を順番に移動することで、この範囲に収まるセグメントのセル内のカウンタを増やします

class CProgram : public CWndCreate
  {
private:
   //--- 表2にデータを書き入れる
   void              SetDataToTable2(void);
  };
//+------------------------------------------------------------------+
//| 表2にデータを書き入れる                                      |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable2(void)
  {
//--- 行が強調表示されていない場合は終了する
   if(m_table1.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a symbol in the table on the left!");
      return;
     }
//--- プログレス開始
   StartProgress();
//--- 表を非表示にする
   m_table2.Hide();
//--- 1番目の表から銘柄を取得する
   string symbol=m_table1.GetValue(0,m_table1.SelectedItem());
   m_symbol.Name(symbol);
//--- 指標データを取得する
   GetIndicatorData(symbol,_Period);
//--- レベルを取得する
   GetLevels();
//--- 表を再構築する
   RebuildingTable2();
//--- 1列目の範囲を設定する
   for(uint r=0; r<(uint)m_table2.RowsTotal(); r++)
      m_table2.SetValue(0,r,(string)m_levels_array[r],0);
//--- 2列目の値を取得する
   int items_total=::ArraySize(m_levels_array);
   ataint segments_total=m_zz.SegmentsTotal();
   for(int i=0; i<items_total-1; i++)
     {
      //--- プログレス
      m_progress_bar.LabelText("Get data ["+(string)m_levels_array[i]+"]...");
      m_progress_bar.Update(i,m_table2.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size=int(m_zz.SegmentSize(s)/m_symbol.Point());
         if(size>m_levels_array[i] && size<m_levels_array[i+1])
           {
            int value=(int)m_table2.GetValue(1,i)+1;
            m_table2.SetValue(1,i,(string)value,0);
           }
        }
     }
//--- 表を表示する
   m_table2.Update(true);
//--- プログレスを終了する
   EndProgress();
  }

例として、2010年から現在までのEUR5のセグメントをM5チャートで受け取りましょう。100の5桁のポイントのステップで範囲を設定します。結果は下のスクリーンショットに表示されています。

セグメントの総数は302145です。 ご覧のとおり、最大セグメント数は0から100の範囲内です。さらに、セグメント数はレベルごとに減少します。指定された期間内に、最大セグメントサイズが2400の5桁のポイントに達しました。 

 図14 サイズ別セグメント数の計算結果

図14 サイズ別セグメント数の計算結果


セグメント数を期間別に数える

形成されたグループ内のセグメントの期間を知っておくとよいでしょう。パターンを見つけるには、分析されたデータに関するすべての統計情報が必要です。別のEAバージョンを開発しましょう。前のセクションからプログラムをコピーして、GUIに表をあと1つ追加するだけです。この表には、(1)バーの数および(2)そのバーの数を有するセグメントの数の2列があります。アプリケーションがチャートにアップロードされた直後にGUIは以下のように見えます。

 図15 セグメント数を長さ別に数えるプログラム

図15 セグメント数を長さ別に数えるプログラム

すべての表のデータを受信するための一連のアクションは、次のとおりです。

以下は、データを受信して3番目の表に書き入れるための CProgram::SetDataToTable3()メソッドのコードを示しています。ここで強調表示されている行は、範囲を受け取るために使用されています。その範囲内で、セグメントの数が長さ別に計算されます。表の行数は、取得されたデータセットの中で最も長い(バー単位)セグメントによって定義されます。表の2列目に書き入れるときは、すべての行を移動して、選択した範囲とサイズ別のバー数に合ったセグメントを数えます。

class CProgram : public CWndCreate
  {
private:
   //--- 表3にデータを書き入れ
   void              SetDataToTable3(void);
  };
//+------------------------------------------------------------------+
//| 表3にデータを書き入れ                                            |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable3(void)
  {
//--- 行が強調表示されていない場合は終了する
   if(m_table2.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a range in the table on the left!");
      return;
     }
//--- プログレス開始
   StartProgress();
//--- 表を非表示にする
   m_table3.Hide();
//--- 強調表示された行を取得する
   int selected_row_index=m_table2.SelectedItem();
//--- 範囲
   int selected_range=(int)m_table2.GetValue(0,selected_row_index);
//--- 表を再構築する
   RebuildingTable3();
//--- 値を1番目の列に設定する
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
      m_table3.SetValue(0,r,(string)(r+1),0);
//--- 2番目の列の値を取得する
   ataint segments_total=m_zz.SegmentsTotal();
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
     {
      //--- プログレス
      m_progress_bar.LabelText("Get data ["+(string)r+"]...");
      m_progress_bar.Update(r,m_table3.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size =int(m_zz.SegmentSize(s)/m_symbol.Point());
         int bars =m_zz.SegmentBars(s);
         //---
         if(size>selected_range && 
            size<selected_range+(int)m_step.GetValue() && 
            bars==r+1)
           {
            int value=(int)m_table3.GetValue(1,r)+1;
            m_table3.SetValue(1,r,(string)value,0);
           }
        }
     }
//--- 表を表示する
   m_table3.Update(true);
//--- プログレス終了
   EndProgress();
  }

表の行とリストの行を強調表示すると、ON_CLICK_LIST_ITEM カスタムイベントが生成されます。この場合、2番目の表のIDを持ったイベントの到着を追跡します。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                  |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- 行クリックイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- 表の行がクリックされた
      if(lparam==m_table2.Id())
        {
         //--- 3番目の表のデータを取得する
         SetDataToTable3();
         return;
        }
      //---
      return;
     }
...
  }

1番目の表で新しい銘柄のリストを受信したり、強調表示された新しい銘柄のデータを計算するときは、現在表示されているデータに関する混乱を避けるために、前の計算からの無関係なデータを削除します。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                  |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタンクリックイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- クリックされたボタンをリクエストする
      if(lparam==m_request.Id())
        {
         //--- 1番目の表のデータを取得する
         SetDataToTable1();
         //--- 表から無関係なデータを削除する
         m_table2.DeleteAllRows(true);
         m_table3.DeleteAllRows(true);
         return;
        }
      //--- クリックされたボタンを計算する
      if(lparam==m_calculate.Id())
        {
         //--- 2番目の表のデータを取得する
         SetDataToTable2();
         //--- 表から無関係なデータを削除する
         m_table3.DeleteAllRows(true);
        }
      //---
      return;
     }
...
  }

チャートでEAを起動した後、以下に示すような結果が得られます。この場合は、USDを特徴とする通貨ペアのリストを作成しました。2018年の初めからのGBPUSDのデータがその後受け取られ、範囲リスト(2番目の表)は100のステップで形成され、それぞれのセグメントが計算されました。一例として、200の範囲と1922 のセグメント数(200~300)の行が2番目の表で強調表示されています。3番目の表には、2番目の表で強調表示されている範囲からすべてのセグメントの期間が表示されます。たとえば、この期間にGBPUSDに表示されるのは、指定された10バーの範囲からの11バーだけでした。

 図16 セグメント数を長さ別に計算した結果

図16 セグメント数を長さ別に計算した結果


グラフィカルインタフェースを使った作業の詳細

補足として、MQLプログラムでGUIが使用されているときにチャート銘柄と時間枠を変更するイベントを適切に処理する方法を示したいと思います。GUIには複数のさまざまなコントロールが含まれている可能性があるため、セット全体をアップロードして初期化するのには時間がかかる場合があります。この時間は時折節約することができます。これはまさにチャート銘柄と時間枠を変えるときにも同じです。ここでは、GUIを常に削除して何度も作成する必要はありません。

これは次のようにして実現できます。

プログラムのメインクラスに、最後のプログラムの初期化解除の理由を格納するためのフィールドを作成します。

class CProgram : public CWndCreate
  {
private:
   //--- 最後の初期化解除の理由
   int               m_last_deinit_reason;
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE)
  {
  }

初期化解除中、理由がREASON_CHARTCHANGEである場合を除き、GUIはすべての場合に削除されます。

//+------------------------------------------------------------------+
//| 初期化解除                                                     |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
//--- 最後の初期化解除の理由を記憶する
   m_last_deinit_reason=reason;
//--- 理由が銘柄と期間の変更に関連していない場合は、GUIを削除する
   if(reason!=REASON_CHARTCHANGE)
     {
      CWndEvents::Destroy();
     }
  }

CProgram::CreateGUI()メソッドを呼び出してプログラムを初期化するときにGUIが作成されるため、最後に非初期化解除の原因を確認するだけで十分です。その理由が銘柄や時間枠の変更であれば、GUIを作成する必要はありません。代わりに、すべてが正常であることを通知してメソッドを終了します。

//+------------------------------------------------------------------+
//| GUIを作成する                                                    |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- チャートまたは時間枠が変更された場合は終了する
   if(m_last_deinit_reason==REASON_CHARTCHANGE)
      return(true);
...
   return(true);
  }


終わりに

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

次回は、これらの記事で開発したツールを使用して他のどのようなデータを取得できるかを説明します。

ファイル名 コメント
MQL5\Indicators\Custom\ZigZag\FrequencyChangeZZ.mq5 反対方向のジグザグ指標セグメント形成の頻度を計算するための指標
MQL5\Indicators\Custom\ZigZag\SumSegmentsZZ.mq5 得られたセットとその平均値からセグメントの合計を計算するための指標
MQL5\Indicators\Custom\ZigZag\PercentageSegmentsZZ.mq5 セグメント合計のパーセンテージ比率とそれらの差の指標
MQL5\Indicators\Custom\ZigZag\MultiPercentageSegmentsZZ.mq5 反対方向のセグメント合計のパーセンテージ比率間の差を使用して、より長い時間枠からのいくつかのセグメントの形成の性質を定義するための指標
MQL5\Experts\ZigZag\TestZZ_05.mq5 MultiPercentageSegmentsZZ指標をテストするためのEA
MQL5\Experts\ZigZag\ZZ_Scanner_01.mq5 PercentageSegmentsZZ指標の統計を収集するためのEA
MQL5\Experts\ZigZag\ZZ_Scanner_02.mq5 いろいろな価格帯でセグメントを計算するためのEA
MQL5\Experts\ZigZag\ZZ_Scanner_03.mq5  さまざまな価格帯に存在するさまざまな期間を持つセグメントを計算するためのEA