データ配列間の相関を解析するためのCGraphicに基づくPairPlot グラフ (時系列)

Dmitriy Gizlyk | 26 10月, 2018

コンテンツ

イントロダクション

当たり前ですが、FXトレーダーは、1つの国の通貨が別の通貨で表現されている通貨ペアというものを使用して取引を行います。 同じ通貨を他の通貨ペアで確認するのは簡単です。 通貨の価値は国の経済状態によって決定されますが、国の経済の変動が他の通貨ペアに均等に影響を与えるのかどうかという点に疑問に思うかもしれません。 おそらくその点は否めないでしょう。 しかし、一国だけの経済状態が変わるような完璧な場合にだけ、それは正当化されます。 一つわかることは、世界が絶えず変化していることです。 1つの国の経済の変動は直接または間接的に世界経済の変動を伴ないます。

インターネット上では、さまざまなペアの通貨価格の変更を分析し、異なる通貨ペア間の相関関係を検索するための情報をいくらでも見つけることができます。 このサイトでは、それに関連したトレード通貨ペアバスケット [1, 2, 3] についての記事があります。 それにも関わらず、時系列間の相関関係を分析するという問題は、未解決のままです。 この記事では、時系列間の相関関係をグラフィカルに分析するためのツールを開発し、分析された通貨ペアの時系列クオートの相関関係を視覚化します。


1. タスクの設定

タスクを始める前に、目標を定義しましょう。 最終的にどのようなツールを入手したいでしょうか。 まず第一に、渡された時系列の間の相関関係のグラフを含むグラフィカルなパネルでなければなりません。 このツールは、十分な汎用性と時間シリーズの異なる数で動作することができる必要があります。

パネルの時系列を解析するために、時系列ごとに分布ヒストグラムを作成します。 また、相関関係を検索するために、解析された時系列に対してペアで表示する散布図も用意します。 トレンド線は、散布図に視覚的な参照として追加されます。

クロステーブルの形でグラフのレイアウトは、ツール全体の読みやすさを向上させます。 このアプローチは、データのプレゼンテーションを統一し、視覚的な知覚を簡素化します。 提案されたツールのレイアウトを以下に示します。

レイアウト


2. 基本クラスの作成

2.1. "基本 "

このようなツールを開発している間にも、ユーザーが異なる数のトレーディング・ツールで動作する可能性があることに注意してください。 完璧な視覚的なソリューションは、標準のグラフは、一般的な相関テーブルを構築するための "レンガ " として使用するブロックベースの表現であると考えています。

チャートの構築の基礎を準備することにより、ツールの開発を開始します。 MetaTrader5は、科学的なグラフを構築するための CGraphic クラスを備えています。 この記事 [4]では、このクラスの詳細な説明を提供しています。 グラフを構築するための基礎としてこれを使用します。 CPlotBase 基本クラスを作成し、CGraphic 標準クラスから派生したものとして割り当てます。 このクラスでは、グラフキャンバスを作成するためのメソッドを作成します。 2つのそのようなメソッドがある: 最初の1つは与えられた側面の次元の正方形のグラフ分野をプロットし、2番目は与えられた座標を使用して長方形区域を組み立てることです。 また、グラフの両側にテキストを表示するためのメソッドを追加します (ツールの名前を表示)。 その上、時系列グラフの表示色を変更するメソッドを追加してみましょう。

また、適用された CGraphic 基本クラスが CObject クラスから派生しておらず、オブジェクトをグラフに配置、非表示、および表示するためのメソッドが含まれていないことにも注意してください。 同様のメソッドは、グラフィカルパネルで広く使用されています。 したがって、グラフィカルパネルを構築するための標準クラスとツールの互換性のために、作成されたクラスにメソッドを追加する必要があります。

class CPlotBase : public CGraphic
  {
protected:
   long              m_chart_id;                //チャート ID
   int               m_subwin;                  //チャートサブウィンドウ

public:
                     CPlotBase();
                    ~CPlotBase();
//---オブジェクトの作成
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int size);
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
//---timeserie のラベルの色を変更する
   virtual bool      SetTimeseriesColor(uint clr, uint timeserie=0);
//---チャートにテキストを追加する
   virtual void      TextUp(string text, uint clr);
   virtual void      TextDown(string text, uint clr);
   virtual void      TextLeft(string text, uint clr);
   virtual void      TextRight(string text, uint clr);
//---ジオメトリ
   virtual bool      Shift(const int dx,const int dy);
//---状態
   virtual bool      Show(void);
   virtual bool      Hide(void);
  };

クラスコンストラクタで、グラフの凡例の表示を削除し、軸に沿ったラベルの最小数を設定します。

CPlotBase::CPlotBase()
  {
   HistoryNameWidth(0);
   HistorySymbolSize(0);
   m_x.MaxLabels(3);
   m_y.MaxLabels(3);
  }

添付ファイルに、すべてのクラスメソッドのコード全体があります。

2.2. 散布図

次に、散布図を表示するための CScatter クラスを開発します。 このクラスには、時系列データを作成および更新するための2つのメソッドのみが含まれます。

class CScatter : public CPlotBase
  {

public:
                     CScatter();
                    ~CScatter();
//---
   int               AddTimeseries(const double &timeseries_1[],const double &timeseries_2[]);
   bool              UpdateTimeseries(const double &timeseries_1[],const double &timeseries_2[],uint timeserie=0);

  };

散布図を基にした解析対象の2つの時系列配列は、AddTimeseries 曲線の作成メソッドに渡されます。 CGraphic 標準クラスは、2つのデータ配列に基づいてポイントグラフを表示できます。 この関数を使用します。 メソッドの先頭で、2つのデータ配列に基づいてポイントカーブを作成します。 曲線の作成に失敗した場合は、結果 "-1 " で関数を終了します。 カーブが正常に作成された場合は、カーブポイントのサイズを設定し、トレンドラインの表示フラグを設定します。 すべての操作を実行した後、メソッドは作成された曲線のインデックスを返します。

int CScatter::AddTimeseries(const double &timeseries_1[],const double &timeseries_2[])
  {
   CCurve *curve=CGraphic::CurveAdd(timeseries_1,timeseries_2,CURVE_POINTS);
   if(curve==NULL)
      return -1;
   curve.PointsSize(2);
   curve.TrendLineVisible(true);
   return (m_arr_curves.Total()-1);
  }

カーブデータを更新するには、UpdateTimeseries メソッドを作成します。 曲線を作成するための2つのデータ配列と、その曲線のインデックスを変更する必要があるデータが渡されます。 この関数の先頭で、指定された曲線番号の妥当性を確認します。 番号が誤って指定された場合は、' false ' で関数を終了します。

次に、受信した時系列の次元を比較します。 配列のサイズが異なる場合や配列が空の場合は、関数を ' false ' で終了します。

次の手順では、インデックスによって曲線オブジェクトへのポインタを指定します。 ポインタが正しくない場合は、関数を ' false ' で終了します。

すべてのチェックの後、曲線に時系列を渡し、' true ' で関数を終了します。

bool CScatter::UpdateTimeseries(const double &timeseries_1[],const double &timeseries_2[], uint timeserie=0)
  {
   if((int)timeserie>=m_arr_curves.Total())
      return false;
   if(ArraySize(timeseries_1)!=ArraySize(timeseries_2) || ArraySize(timeseries_1)==0)
      return false;
//---
   CCurve *curve=m_arr_curves.At(timeserie);
   if(CheckPointer(curve)==POINTER_INVALID)
      return false;
//---
   curve.Update(timeseries_1,timeseries_2);
//---
   return true;
  }

2.3. ヒストグラム

ヒストグラムは、ツールを構築するための別の "レンガ" です。 CHistogram クラスを作成する必要があります。 CScatter と同様に、このクラスは独自の曲線データ生成および更新メソッドを受け取ります。 ただし、その前身とは異なり、現在のクラスは、曲線を構築する1つの時系列を適用します。 このメソッドを構築する原則は、前のクラスのメソッドに似ています。

CGraphic 基本クラスは、標準形式でのみヒストグラムを作成できることに注意してください。 相場プロファイルタイプの垂直ヒストグラムを構築する可能性を追加するには、HistogramPlot メソッドを書き換える必要があります。 さらに、ヒストグラムの作図タイプを格納するため e_orientation 変数を追加し、ヒストグラムタイプを指定する関数を追加して、グラフキャンバス作成メソッドを再書き込みする必要があります。

CGpraphic のクラスと基本クラスのもう1つの差は、取得する初期データの種類です。 基本クラスでは、取得した値の配列をグラフへの直接出力に使用します。 このクラスは、時系列を受信し、ヒストグラムを処理する前に、取得したデータを処理する必要があります。 ヒストグラムを構築するためのデータの準備は、CalculateHistogramArray メソッドによって行われ、ヒストグラム列の数は SetCells メソッドによって設定され、i_cells 変数に保存されます。

class CHistogram : public CPlotBase
  {
private:
   ENUM_HISTOGRAM_ORIENTATION    e_orientation;
   uint                          i_cells;

public:
                                 CHistogram();
                                ~CHistogram();
//---
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int size, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);
   int               AddTimeserie(const double &timeserie[]);
   bool              UpdateTimeserie(const double &timeserie[],uint timeserie=0);
   bool              SetCells(uint value)    {  i_cells=value; }

protected:
   virtual void      HistogramPlot(CCurve *curve);
   bool              CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[], 
                                             double &maxv,double &minv);
};

CalculateHistogramArray メソッドは、 MQL5 リファレンスで提供されているアルゴリズムに基づいて、小さな加算を行います。 メソッドの冒頭では、ヒストグラムをプロットするための初期データの充足を確認し、最小値と最大数を決定し、各間隔の範囲の幅を計算し、間隔と周波数を格納するための配列を準備します。

その後、インターバルセンタはループ内に設定され、周波数配列はゼロに設定されます。

次のループでは、時系列を繰り返し処理し、対応する間隔に適合する値をカウントします。

最後に、前述の継ぎ手を時系列の要素の合計数のパーセンテージに変換する周波数を正規化します。

bool CHistogram::CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[], 
                             double &maxv,double &minv) 
  { 
   int size=ArraySize(data); 
   if(size<(int)i_cells*10) return (false); 
   minv=data[ArrayMinimum(data)]; 
   maxv=data[ArrayMaximum(data)]; 
   double range=maxv-minv; 
   double width=range/i_cells; 
   if(width==0) return false; 
   ArrayResize(intervals,i_cells); 
   ArrayResize(frequency,i_cells); 
//---インターバルセンタの設定 
   for(uint i=0; i<i_cells; i++) 
     { 
      intervals[i]=minv+(i+0.5)*width; 
      frequency[i]=0; 
     } 
//--- fill in the interval fitting frequencies 
   for(int i=0; i<size; i++) 
     { 
      uint ind=int((data[i]-minv)/width); 
      if(ind>=i_cells) ind=i_cells-1; 
      frequency[ind]++; 
     } 
//---周波数をパーセンテージに正規化する
   for(uint i=0; i<i_cells; i++) 
      frequency[i]*=(100.0/(double)size); 
   return (true); 
  } 

このヒストグラムは、HistogramPlot メソッドによってグラフにプロットされます。 この関数は、時系列の使用とヒストグラムの作図に修正された CGraphic 基本クラスのアルゴリズムに基づいて作られています。

このメソッドの最初に、ヒストグラムをプロットするためのデータを準備します。 これを行うには、曲線データから時系列を取得し、CalculateHistogramArray メソッドを呼び出します。 関数が正常に実行された後、ヒストグラムブロックの幅を取得し、構築データ配列のサイズを確認してください。

次に、ヒストグラム表示タイプに従って、値を軸でフォーマットします。

最後に、グラフフィールドにダイアグラム列を表示するためのループを配置します。

CHistogram::HistogramPlot(CCurve *curve)
  {
   double data[],intervals[],frequency[];
   double max_value, min_value;
   curve.GetY(data);
   if(!CalculateHistogramArray(data,intervals,frequency,max_value,min_value))
      return;
//---historgram パラメータ
   int histogram_width=fmax(curve.HistogramWidth(),2);
//---チェック
   if(ArraySize(frequency)==0 || ArraySize(intervals)==0)
      return;
//---
   switch(e_orientation)
     {
      case HISTOGRAM_HORIZONTAL:
        m_y.AutoScale(false);
        m_x.Min(intervals[ArrayMinimum(intervals)]);
        m_x.Max(intervals[ArrayMaximum(intervals)]);
        m_x.MaxLabels(3);
        m_x.ValuesFormat("%.0f");
        m_y.Min(0);
        m_y.Max(frequency[ArrayMaximum(frequency)]);
        m_y.ValuesFormat("%.2f");
        break;
      case HISTOGRAM_VERTICAL:
        m_x.AutoScale(false);
        m_y.Min(intervals[ArrayMinimum(intervals)]);
        m_y.Max(intervals[ArrayMaximum(intervals)]);
        m_y.MaxLabels(3);
        m_y.ValuesFormat("%.0f");
        m_x.Min(0);
        m_x.Max(frequency[ArrayMaximum(frequency)]);
        m_x.ValuesFormat("%.2f");
        break;
     }
//---
   CalculateXAxis();
   CalculateYAxis();
//---y の元の計算
   int originalY=m_height-m_down;
   int originalX=m_width-m_right;
   int yc0=ScaleY(0.0);
   int xc0=ScaleX(0.0);
//---曲線の色を取得
   uint clr=curve.Color();
//---描画 
   for(uint i=0; i<i_cells; i++)
     {
      //---座標を確認する
      if(!MathIsValidNumber(frequency[i]) || !MathIsValidNumber(intervals[i]))
         continue;
      if(e_orientation==HISTOGRAM_HORIZONTAL)
        {
         int xc=ScaleX(intervals[i]);
         int yc=ScaleY(frequency[i]);
         int xc1 = xc - histogram_width/2;
         int xc2 = xc + histogram_width/2;
         int yc1 = yc;
         int yc2 = (originalY>yc0 && yc0>0) ? yc0 : originalY;
         //---
         if(yc1>yc2)
            yc2++;
         else
            yc2--;
         //---
         m_canvas.FillRectangle(xc1,yc1,xc2,yc2,clr);
        }
      else
        {
         int yc=ScaleY(intervals[i]);
         int xc=ScaleX(frequency[i]);
         int yc1 = yc - histogram_width/2;
         int yc2 = yc + histogram_width/2;
         int xc1 = xc;
         int xc2 = (originalX>xc0 && xc0>0) ? xc0 : originalX;
         //---
         if(xc1>xc2)
            xc2++;
         else
            xc2--;
         //---
         m_canvas.FillRectangle(xc1,yc1,xc2,yc2,clr);
        }
     }
//---
  }

すべてのクラスとメソッドの完全なコードは、添付ファイルで使用できます。

2.4. 時系列を操作するためのクラス

このツールを構築するには、必要なヒストリーデータをダウンロードし、グラフをプロットするための時系列を準備する別の "ブリック" が必要です。 このタスクは、CTimeserie クラスで実行されます。 クラスを初期化するときに、ツール名、時間枠、適用価格が渡されます。 その上、ツール名、時間枠、適用された価格およびヒストリーデプスに続く変更のメソッドが作成されるべきです。

class CTimeserie :  public CObject
  {
protected:
   string               s_symbol;
   ENUM_TIMEFRAMES      e_timeframe;
   ENUM_APPLIED_PRICE   e_price;
   double               d_timeserie[];
   int                  i_bars;
   datetime             dt_last_load;
   
public:
                     CTimeserie(void);
                    ~CTimeserie(void);
   bool              Create(const string symbol=NULL, const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
//---時系列の設定を変更する
   void              SetBars(const int value)            {  i_bars=value;  }
   void              Symbol(string value)                {  s_symbol=value;      dt_last_load=0;  }
   void              Timeframe(ENUM_TIMEFRAMES value)    {  e_timeframe=value;   dt_last_load=0;  }
   void              Price(ENUM_APPLIED_PRICE value)     {  e_price=value;       dt_last_load=0;  }
//---
   string            Symbol(void)                        {  return s_symbol;     }
   ENUM_TIMEFRAMES   Timeframe(void)                     {  return e_timeframe;  }
   ENUM_APPLIED_PRICE Price(void)                        {  return e_price;      }
//---データの読み込み
   virtual bool      UpdateTimeserie(void);
   bool              GetTimeserie(double &timeserie[])   {  return ArrayCopy(timeserie,d_timeserie)>0;   }
  };

主なデータ準備タスクは、UpdateTimeserie メソッドで行います。 メソッドの先頭で、必要な情報が現在の足に既に読み込まれているかどうかを確認します。 情報が準備できたら、' true ' を使用して関数を終了します。 データの準備が必要な場合は、指定価格に従って必要なヒストリーデータをアップロードしてください。 情報をアップロードできない場合は、' false ' で関数を終了してください。 価格自体には興味はありませんが、その変更に注意してください。 したがって、ヒストリーデータのアップロードは、指定された量から1つの足分を超えています。 次の段階では、各足の価格変更を再計算し、ループ内の配列に保存します。 後で、ユーザーは GetTimeserie メソッドを使用してこの情報を取得できます。

bool CTimeserie::UpdateTimeserie(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(s_symbol,e_timeframe,SERIES_LASTBAR_DATE);
   if(dt_last_load>=cur_date && ArraySize(d_timeserie)>=i_bars)
      return true;
//---
   MqlRates rates[];
   int bars=0,i;
   double data[];
   switch(e_price)
     {
      case PRICE_CLOSE:
        bars=CopyClose(s_symbol,e_timeframe,1,i_bars+1,data);
        break;
      case PRICE_OPEN:
        bars=CopyOpen(s_symbol,e_timeframe,1,i_bars+1,data);
      case PRICE_HIGH:
        bars=CopyHigh(s_symbol,e_timeframe,1,i_bars+1,data);
      case PRICE_LOW:
        bars=CopyLow(s_symbol,e_timeframe,1,i_bars+1,data);
      case PRICE_MEDIAN:
        bars=CopyRates(s_symbol,e_timeframe,1,i_bars+1,rates);
        bars=ArrayResize(data,bars);
        for(i=0;i<bars;i++)
           data[i]=(rates[i].high+rates[i].low)/2;
        break;
      case PRICE_TYPICAL:
        bars=CopyRates(s_symbol,e_timeframe,1,i_bars+1,rates);
        bars=ArrayResize(data,bars);
        for(i=0;i<bars;i++)
           data[i]=(rates[i].high+rates[i].low+rates[i].close)/3;
        break;
      case PRICE_WEIGHTED:
        bars=CopyRates(s_symbol,e_timeframe,1,i_bars+1,rates);
        bars=ArrayResize(data,bars);
        for(i=0;i<bars;i++)
           data[i]=(rates[i].high+rates[i].low+2*rates[i].close)/4;
        break;
     }
//---
   if(bars<=0)
      return false;
//---
   dt_last_load=cur_date;
//---
   if(ArraySize(d_timeserie)!=(bars-1) && ArrayResize(d_timeserie,bars-1)<=0)
      return false;
   double point=SymbolInfoDouble(s_symbol,SYMBOL_POINT);
   for(i=0;i<bars-1;i++)
      d_timeserie[i]=(data[i+1]-data[i])/point;
//---
   return true;
  }

すべてのクラスとそのメソッドの完全なコードは、添付ファイルで使用できます。


3. PairPlotのアセンブリ

"レンガ " を作成した後、ツールの開発を開始することができます。 CWndClient クラスから派生した CPairPlot クラスを作成してみましょう。 このメソッドでは、標準の CAppDialog クラスを使用して構築されたグラフィカルパネルでツールを使用しやすくなります (アプリケーションの詳細については、[5,6] をご覧ください)。

このクラスの ' private ' ブロックでは、グラフへのポインタを格納するための CPlotBase クラスオブジェクトへのポインタの配列を宣言し、タイムシリーズオブジェクトへのポインタを格納するための CArrayObj クラスオブジェクトだけでなく、適用された時間枠、価格を格納するための変数、グラフのツール名を表示するためのヒストグラムの向き、ヒストリーデプス、および色を宣言します。

class CPairPlot : public CWndClient
  {
private:
   CPlotBase                    *m_arr_graphics[];
   CArrayObj                     m_arr_symbols;
   ENUM_TIMEFRAMES               e_timeframe;
   ENUM_APPLIED_PRICE            e_price;
   int                           i_total_symbols;
   uint                          i_bars;
   ENUM_HISTOGRAM_ORIENTATION    e_orientation;
   uint                          i_text_color;
      
public:
                     CPairPlot();
                    ~CPairPlot();
//---
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
   bool              Refresh(void);
   bool              HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value);
   ENUM_HISTOGRAM_ORIENTATION    HistogramOrientation(void)    {  return e_orientation;   }
   bool              SetTextColor(color value);
//---ジオメトリ
   virtual bool      Shift(const int dx,const int dy);
//---状態
   virtual bool      Show(void);
   virtual bool      Hide(void);
  };

' public ' ブロックでクラスメソッドを宣言します。 このクラスの初期化は、グラフ ID、オブジェクト名、適用されたサブウィンドウインデックス、構築座標、使用されているシンボルの配列、適用したタイムフレーム、価格、ヒストリーデプス、およびその呼び出し時のヒストグラム列の数を受け取る Create メソッドによって実行されます。

メソッドの最初に、渡されたツール名の配列と、指定されたヒストリーデプスを確認します。 最小要件を満たしていない場合は、' false ' で関数を終了します。 次に、グラフをプロットするためのインプットパラメータの値を保存します。

bool CPairPlot::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
   i_total_symbols=0;
   int total=ArraySize(symbols);
   if(total<=1 || bars<100)
      return false;
//---
   e_timeframe=timeframe;
   i_bars=bars;
   e_price=price;

次に、ループ内の各ツールの CTimeserie クラスのインスタンスを作成します。 指定された各ツールの時系列を作成できない場合は、' false ' で関数を終了します。

   for(int i=0;i<total;i++)
     {
      CTimeserie *temp=new CTimeserie;
      if(temp==NULL)
         return false;
      temp.SetBars(i_bars);
      if(!temp.Create(symbols[i],e_timeframe,e_price))
         return false;
      if(!m_arr_symbols.Add(temp))
         return false;
     }
   i_total_symbols=m_arr_symbols.Total();
   if(i_total_symbols<=1)
      return false;

準備タスクが正常に完了したら、グラフィカルオブジェクトの作成に進みます。 まず、親クラスの Create メソッドを呼び出します。 次に、解析されたツールの数に従って、m_arr_graphics 配列のサイズ (グラフへのポインタを格納するための) を持ってきます。 ツール全体のサイズと解析されたツールの数に基づいて、各グラフの幅と高さを計算します。

その後、解析されたすべてのツールを繰り返し処理し、グラフィカルオブジェクトを使用してテーブルを作成するための2つのネストループを配置します。 他のケースでは、同じ名前のツールと散布図のクロスにヒストグラムを作成します。 すべてのオブジェクトが正常に作成された場合は、' true ' を使用してメソッドを終了します。

   if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2))
      return false;
//---
   if(ArraySize(m_arr_graphics)!=(i_total_symbols*i_total_symbols))
      if(ArrayResize(m_arr_graphics,i_total_symbols*i_total_symbols)<=0)
         return false;
   int width=Width()/i_total_symbols;
   int height=Height()/i_total_symbols;
   for(int i=0;i<i_total_symbols;i++)
     {
      CTimeserie *timeserie1=m_arr_symbols.At(i);
      if(timeserie1==NULL)
         continue;
      for(int j=0;j<i_total_symbols;j++)
        {
         string obj_name=m_name+"_"+(string)i+"_"+(string)j;
         int obj_x1=m_rect.left+j*width;
         int obj_x2=obj_x1+width;
         int obj_y1=m_rect.top+i*height;
         int obj_y2=obj_y1+height;
         if(i==j)
           {
            CHistogram *temp=new CHistogram();
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2,e_orientation))
               return false;
            m_arr_graphics[i*i_total_symbols+j]=temp;
            temp.SetCells(cells);
           }
         else
           {
            CScatter *temp=new CScatter();
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2))
               return false;
            CTimeserie *timeserie2=m_arr_symbols.At(j);
            if(timeserie2==NULL)
               continue;
            m_arr_graphics[i*i_total_symbols+j]=temp;
           }
        }
     }
//---
   return true;
  }

Refresh メソッドは、時系列データを更新し、グラフにデータを表示するために使用します。 このメソッドの先頭には、すべての時系列のデータを更新するためのループが用意されています。 分布グラフを作成すると、時系列がペアで使用することに注意してください。 したがって、データは同等である必要があります。 データの互換性を確保するために、グラフィカルオブジェクトデータは更新されず、少なくとも1つの時系列の更新時にエラーが発生した場合に、メソッドは ' false ' を返します。

時系列データを更新した後、更新された時系列をグラフィカルオブジェクトに渡すためのループが配置されます。 ' false ' パラメータを使用して、グラフィカルオブジェクトの Update メソッドを呼び出すことに注意してください。 このような呼び出しにより、アプリケーションが実行されているグラフを更新せずに、グラフィカルオブジェクトが更新されます。 このメソッドでは、各グラフィカルオブジェクトを更新した後、グラフの更新を除外して、ターミナルの負荷を減らし、関数の実行時間を短縮します。 このグラフは、関数を終了する前にすべてのグラフィック要素を更新した後に更新されます。

bool CPairPlot::Refresh(void)
  {
   bool updated=true;
   for(int i=0;i<i_total_symbols;i++)
     {
      CTimeserie *timeserie=m_arr_symbols.At(i);
      if(timeserie==NULL)
         continue;
      updated=(updated && timeserie.UpdateTimeserie());
     }
   if(!updated)
      return false;
//---
   for(int i=0;i<i_total_symbols;i++)
     {
      CTimeserie *timeserie1=m_arr_symbols.At(i);
      if(CheckPointer(timeserie1)==POINTER_INVALID)
         continue;
      double ts1[];
      if(!timeserie1.GetTimeserie(ts1))
         continue;
//---
      for(int j=0;j<i_total_symbols;j++)
        {
         if(i==j)
           {
            CHistogram *temp=m_arr_graphics[i*i_total_symbols+j];
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            if(temp.CurvesTotal()==0)
              {
               if(temp.AddTimeserie(ts1)<0)
                  continue;
              }
            else
              {
               if(!temp.UpdateTimeserie(ts1))
                  continue;
              }
            if(!temp.CurvePlotAll())
               continue;
            if(i==0)
               temp.TextUp(timeserie1.Symbol(),i_text_color);
            if(i==(i_total_symbols-1))
               temp.TextDown(timeserie1.Symbol(),i_text_color);
            if(j==0)
               temp.TextLeft(timeserie1.Symbol(),i_text_color);
            if(j==(i_total_symbols-1))
               temp.TextRight(timeserie1.Symbol(),i_text_color);
            temp.Update(false);
           }
         else
           {
            CScatter *temp=m_arr_graphics[i*i_total_symbols+j];
            if(CheckPointer(temp)==POINTER_INVALID)
               return false;
            CTimeserie *timeserie2=m_arr_symbols.At(j);
            if(CheckPointer(timeserie2)==POINTER_INVALID)
               continue;
            double ts2[];
            if(!timeserie2.GetTimeserie(ts2))
               continue;
            if(temp.CurvesTotal()==0)
              {
               if(temp.AddTimeseries(ts1,ts2)<0)
                  continue;
              }
            else
               if(!temp.UpdateTimeseries(ts1,ts2))
                  continue;
            if(!temp.CurvePlotAll())
               continue;
            if(i==0)
               temp.TextUp(timeserie2.Symbol(),i_text_color);
            if(i==(i_total_symbols-1))
               temp.TextDown(timeserie2.Symbol(),i_text_color);
            if(j==0)
               temp.TextLeft(timeserie1.Symbol(),i_text_color);
            if(j==(i_total_symbols-1))
               temp.TextRight(timeserie1.Symbol(),i_text_color);
            temp.Update(false);
           }
        }
     }
//---
   ChartRedraw(m_chart_id);
//---
   return true;
  }

以前、グラフィカル要素が CObject クラスから派生していない CGraphic クラスに基づいていることを既に説明しました。 このため、CPlotBase 基本クラスへのメソッドのシフト、非表示、および表示を追加しました。 同じ理由で、また、CPairPlot クラスの対応するメソッドを書き換える必要があります。 すべてのクラスとそのメソッドの完全なコードは、添付ファイルで使用できます。


4. CPairPlot クラスの使用例

さて、いろいろ終えたので結果を見てみましょう。 ツールを実証するためには、それぞれの新しい足で、例えば、最後の1000ロウソク足の相関グラフを表示するインジケータを作ってみましょう。

既に説明したように、このツールはグラフィカルなパネルで使用できるように構築されています。 したがって、まず CAppDialog クラスから派生した CPairPlotDemo クラスを作成してみましょう。 CAppDialog クラスの操作の詳細については、[5, 6] の記事をご覧ください。 ここでは、ツールを使用しての特殊性を指摘します。

CPairPlot クラスのインスタンスを ' private ' ブロックで宣言します。 ' public ' ブロックで、ツールの初期化と操作に必要なすべてのインプットパラメータを使用して Create メソッドを宣言します。 ここでは、また、ツールの対応するメソッドを呼び出しますリフレッシュと HistogramOrientation メソッドを宣言します。

class CPairPlotDemo : public CAppDialog
  {
private:
   CPairPlot         m_PairPlot;
public:
                     CPairPlotDemo();
                    ~CPairPlotDemo();
//---
   bool              Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2,const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10);
   bool              Refresh(void);
//---
   bool              HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value)   {  return m_PairPlot.HistogramOrientation(value);   }
   ENUM_HISTOGRAM_ORIENTATION    HistogramOrientation(void)                   {  return m_PairPlot.HistogramOrientation();   }
   };

Create メソッドでは、まず親クラスの適切なメソッドを呼び出し、次に要素インスタンスの同じメソッドを呼び出し、CPairPlot クラスインスタンスへのポインタをコントロール要素のコレクションに追加します。

bool CPairPlotDemo::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2,const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return false;
   if(!m_PairPlot.Create(m_chart_id,m_name+"PairPlot",m_subwin,0,0,ClientAreaWidth(),ClientAreaHeight(),symbols,timeframe,bars,cells))
      return false;
   if(!Add(m_PairPlot))
      return false;
//---
   return true;
  }

さて、インジケータを作成してみましょう。 使用するツールのコンマ区切りの名前を持つ文字列、足の分析ヒストリーデプス、列数、ヒストグラムの向きは、インジケータのインプットパラメータとして使用します。

input string   i_Symbols   =  "EURUSD, GBPUSD, EURGBP";
input uint     i_Bars      =  1000;
input uint     i_Cells     =  50;
input ENUM_HISTOGRAM_ORIENTATION i_HistogramOrientation  =  HISTOGRAM_HORIZONTAL;

グローバル変数で CPairPlotDemo クラスのインスタンスを宣言します。

CPairPlotDemo     *PairPlot;

この関数では、インジケータの外部パラメータの文字列から、適用されたツールの配列を作成します。 次に、CPairPlotDemo クラスのインスタンスを作成し、指定されたヒストグラムの向きを渡して、create メソッドを呼び出します。 初期化が正常に終了したら、Run メソッドによってクラスの実行を開始し、Refresh メソッドを使用してデータを更新します。

int OnInit()
  {
//---
   string symbols[];
   int total=StringSplit(i_Symbols,',',symbols);
   if(total<=0)
      return INIT_FAILED;
   for(int i=0;i<total;i++)
     {
      StringTrimLeft(symbols[i]);
      StringTrimRight(symbols[i]);
     }
//---
   PairPlot=new CPairPlotDemo;
   if(CheckPointer(PairPlot)==POINTER_INVALID)
      return INIT_FAILED;
//---
   if(!PairPlot.HistogramOrientation(i_HistogramOrientation))
      return INIT_FAILED;
   if(!PairPlot.Create(0,"Pair Plot",0,20,20,620,520,symbols,PERIOD_CURRENT,i_Bars,i_Cells))
      return INIT_FAILED;
   if(!PairPlot.Run())
      return INIT_FAILED;
   PairPlot.Refresh();
//---
   return INIT_SUCCEEDED;
  }

OnCalculate 関数では、新しい足ごとに Refresh メソッドを呼び出します。 適切なクラスメソッドは、OnChartEvent および OnDeinit 関数から呼び出されます。

添付ファイル内にすべての関数とクラスのコード全体があります。

以下で、インジケータがどのように動作するかを見ることができます。

PairPlot 操作


結論

この記事では、興味深いツールを提供しました。 トレーダーが迅速かつ容易にトレードツールのほぼ任意の数の相関関係を視覚化することができます。 このツールの主な目的は、トレーディングツールの分析と様々なアービトラージ戦略の開発にあります。 もちろん、このツールだけでは本格的なトレードシステムの開発には不十分ですが、トレードシステムの開発の第一段階を容易にします。例えば、相関ツールとその依存関係の検索です。


レファレンス

  1. 外国為替相場での通貨バスケットでのタスク
  2. 通貨ペアバスケットをトレードするときに発生するパターンをテスト パート I
  3. 通貨ペアバスケットをトレードするときに発生するパターンをテスト パート II
  4. 視覚化! R 言語の「プロット」に似た MQL5 グラフィックライブラリ
  5. 複雑なレベルのグラフィカルなパネルを作成する方法
  6. パネルの改善: 透明性の追加、背景色の変更、CAppDialog/CWndClient からの継承


この記事で使用したプログラム

#
 名前
タイプ 
詳細 
1  PlotBase.mqh  クラスライブラリ  グラフを作成するための基本クラス
2  Scatter.mqh  クラスライブラリ  散布図を作成するためのクラス
3  Histogram.mqh  クラスライブラリ  ヒストグラムを作成するためのクラス
4  PairPlot.mqh  クラスライブラリ  PairPlot ツールクラス
5  PairPlotDemo.mqh  クラスライブラリ  ツール接続をデモンストレーションするためのクラス
6  PairPlot.mq5  インジケータ  ツールのタスクを示すインジケータ