English Deutsch
preview
MQL5における二変量コピュラ(第1回):依存関係モデリングのための正規コピュラおよびtコピュラの実装

MQL5における二変量コピュラ(第1回):依存関係モデリングのための正規コピュラおよびtコピュラの実装

MetaTrader 5 |
36 0
Francis Dube
Francis Dube

はじめに

コピュラを用いた取引戦略は、資産間の依存関係をコピュラ関数によってモデル化することで、統計的裁定取引に対する有力な代替アプローチを提供します。従来のペア取引は、長期的な関係性からの一時的な乖離を前提としていますが、コピュラを用いることで、この関係性をより柔軟にモデル化することで、取引機会を識別することが可能になります。コピュラを利用する理論的な利点は、資産間の非線形かつ非対称な依存関係を捉えられる点にあります。

このような背景を踏まえ、本記事はコピュラベースの取引戦略を実装するためのツールを紹介する連載の始まりとなります。本記事では、ペア取引および依存関係モデリングの文脈において、統計的コピュラの基礎を解説します。また、コピュラモデルを適合する前段階として必要となる、データセットの前処理に関するMQL5コードも紹介します。連載全体を通じての主な目的は、一般的に使用されるコピュラモデルを実装したライブラリを構築することです。その第一歩として、本記事では正規コピュラとtコピュラの実装を紹介します。


コピュラ理論の基礎要素

コピュラを扱うためには、以下の3つの関連概念を理解する必要があります。

確率密度関数:統計分布の研究において、確率密度関数(PDF)は、連続確率変数Xが特定の値xを取る「起こりやすさ」を記述します。離散型の確率質量関数とは異なり、PDFは特定の値に直接確率を割り当てるものではありません。その代わりに密度を表し、Xが区間[a,b]に含まれる確率は、その区間におけるPDFの積分として与えられます。

累積分布関数:PDFを基礎として構築される累積分布関数(CDF)は、確率変数Xがx以下の値を取る確率を表します。CDFは0から1の範囲を取る単調非減少関数であり、確率変数の分布を完全に記述します。

周辺分布と同時分布:複数の確率変数を扱う場合、周辺分布と同時分布を区別する必要があります。周辺分布は、他の変数の影響を無視して、集合内の単一変数の確率分布を記述します。一方、同時分布は、データセットに含まれるすべての変数の結合的な確率分布を特徴付けます。  

確率積分変換(PIT):確率積分変換(PIT)は確率論における基本概念であり、コピュラを理解するための重要な構成要素です。これは、連続確率変数Xが累積分布関数F(X)を持つ場合、変換後の変数U=F(X)は区間[0,1]上の一様分布に従うことを示します。つまり、Xが正規分布、指数分布、またはその他の連続分布に従うかに関わらず、自身のCDFを適用することで[0,1]上の一様分布を持つ変数に変換されます。

確率積分変換

PITは、任意の連続確率変数を単位区間に写像し、確率構造を保持したまま標準化することを可能にします。周辺分布を一様分布に変換することで、各変数固有の分布形状の影響を受けずに変数間の結びつき(依存構造)を分析できます。この一様空間こそがコピュラが機能する領域であり、コピュラはこの変換後の空間において変数間の依存関係を捉えます。

経験累積分布関数:変数の真の基礎分布が不明な場合、経験累積分布関数(ECDF: Empirical Cumulative Distribution Function)を用いて真のCDFを推定できます。N個の観測値x₁,x₂,…,xₙからなるサンプルに対して、ECDFは次のように定義されます。​

経験累積分布関数

ここで用いられる指示関数は、xᵢ≤xの場合に1、それ以外の場合に0を取ります。

ECDFを用いてPITを実行する方法は、特定の分布形状を仮定しないため、ノンパラメトリック推定と呼ばれます。変換後のデータポイントは、コピュラ自体をフィッティングする際の入力として使用されます。

linear_cdf.mqhファイルには、データセットのECDFを推定するためのCLinearCDFクラスの定義が含まれています。このクラスは、標準的なECDFの段差の間に線形補間を適用したECDFを生成します。これにより、サンプル点では標準ECDFと一致しつつ、全体として滑らかな結果が得られます。

//+------------------------------------------------------------------+
//|                                                   linear_cdf.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include<Arrays/ArrayDouble.mqh>
#include<Arrays/ArrayObj.mqh>
#include "ecdf.mqh"
//+------------------------------------------------------------------+
//|CArray objects                                                    |
//+------------------------------------------------------------------+
class CArrayDoubles:public CArrayObj
  {
   virtual bool      CreateElement(const int index)
     {
      m_data[index] = new CArrayDouble();
      return m_data[index]!=NULL;
     }
  };
//+------------------------------------------------------------------+
//| ecdf quantile transformer                                        |
//+------------------------------------------------------------------+
class CLinearCDF
  {
protected:
   CSpline1DInterpolantShell* m_shells[];
   CArrayDoubles      m_internal;
   CArrayDouble      *m_arrays[];
   double            m_ub,m_lb;
   ulong             m_cols;
   void              clean_up(void)
     {

      for(uint i = 0; i<m_shells.Size(); i++)
         delete m_shells[i];
      for(uint i = 0; i<m_arrays.Size(); i++)
         delete m_arrays[i];
      m_internal.Clear();
     }
public:
                     CLinearCDF(void)
     {
      m_cols = 0;
     }
                    ~CLinearCDF(void)
     {
      clean_up();
     }
   bool              save(const int filehandle)
     {
      if(!m_cols)
         return false;
      return m_internal.Save(filehandle);
     }
   bool              load(const int filehandle)
     {
      if(m_cols)
        {
         clean_up();
         m_cols = 0;
        }

      if(m_internal.Load(filehandle))
        {
         int n_arrays = m_internal.Total();
         m_cols = ulong((n_arrays-1)/2);
         ArrayResize(m_shells,int(m_cols));
         ArrayResize(m_arrays,int(m_cols*2)+1);
         double xa[],ya[];
         m_arrays[0] = m_internal.At(0);
         m_ub = m_arrays[0].At(0);
         m_lb = m_arrays[0].At(1);
         for(int i = 0; i<int(m_cols); i++)
           {
            m_arrays[i*2+1] = m_internal.At(i*2+1);
            m_arrays[i*2+2] = m_internal.At(i*2+2);
            ArrayResize(xa,m_arrays[i*2+1].Total());
            for(int j = 0; j<m_arrays[i*2+1].Total(); j++)
               xa[j] = m_arrays[i*2+1][j];
            ArrayResize(ya,m_arrays[i*2+2].Total());
            for(int j = 0; j<m_arrays[i*2+2].Total(); j++)
               ya[j] = m_arrays[i*2+2][j];
            m_shells[i] = new CSpline1DInterpolantShell();
            CAlglib::Spline1DBuildLinear(xa,ya,m_shells[i]);
           }
         return true;
        }
      else
         return false;
     }
   bool              fit(matrix &data,double upper_bound = 1.0-1.e-5,double lower_bound = 1.e-5)
     {
      if(upper_bound<=lower_bound)
        {
         Print(__FUNCTION__, " invalid inputs ");
         return false;
        }

      if(m_cols)
        {
         clean_up();
         m_cols = 0;
        }

      m_ub = upper_bound;
      m_lb = lower_bound;
      m_cols = data.Cols();
      vector v;
      double xa[],ya[];
      vector slopes;
      vector slope_samples;
      CECDF m_ecdf;
      ArrayResize(m_shells,int(m_cols));
      ArrayResize(m_arrays,int(m_cols*2)+1);
      m_arrays[0] = new CArrayDouble();
      if(!m_arrays[0].Add(m_ub) || !m_arrays[0].Add(m_lb) || !m_internal.Add(m_arrays[0]))
        {
         Print(__FUNCTION__, " error ", GetLastError());
         return false;
        }
      for(uint i = 0; i<m_shells.Size(); i++)
        {
         v = data.Col(i);
         if(!m_ecdf.fit(v))
           {
            Print(__FUNCTION__, " error ");
            return false;
           }

         np::quickSort(v,true,0,long(v.Size()-1));
         slopes = np::unique(v);
         slope_samples = m_ecdf.ecdf(slopes);
         m_shells[i] = new CSpline1DInterpolantShell();
         if(!np::vecAsArray(slopes,xa) || !np::vecAsArray(slope_samples,ya))
           {
            Print(__FUNCTION__, " error ");
            return false;
           }
         m_arrays[i*2+1] = new CArrayDouble();
         m_arrays[i*2+2] = new CArrayDouble();
         if(!m_arrays[i*2+1].AddArray(xa) || !m_arrays[i*2+2].AddArray(ya) ||!m_internal.Add(m_arrays[i*2+1])||!m_internal.Add(m_arrays[i*2+2]))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return false;
           }
         CAlglib::Spline1DBuildLinear(xa,ya,m_shells[i]);
        }

      return true;
     }

   matrix            to_quantile(matrix &in)
     {
      if(in.Cols()!=m_cols)
        {
         Print(__FUNCTION__, " invalid input ");
         return matrix::Zeros(0,0);
        }

      vector temp;
      matrix out(in.Rows(),m_cols);
      for(ulong i = 0; i<m_cols; ++i)
        {
         temp  = in.Col(i);
         for(ulong j = 0; j<in.Rows(); ++j)
            out[j][i] = MathMax(MathMin(CAlglib::Spline1DCalc(m_shells[i],temp[j]),m_ub),m_lb);
        }

      return out;
     }
  };
//+------------------------------------------------------------------+

fit()メソッドは、多変量データセットの変数として解釈される任意の列数を持つ行列を受け取ります。オプションの入力パラメータであるupper_boundおよびlower_boundは、値がちょうど0または1になることを防ぐための制約を定義します。内部処理では、標準的なECDFが一意にソートされたサンプル点で評価されます。その後、これらのサンプル点と対応するECDF値を基に、一次元の線形補間モデルが構築されます。 

//+------------------------------------------------------------------+
//|                                                    ECDF_Demo.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<ECDF/linear_cdf.mqh>
//--- input parameters
input string   FirstSymbol = "NZDUSD";
input string   SecondSymbol = "AUDUSD";
input ENUM_TIMEFRAMES TimeFrame = PERIOD_D1;
input datetime StartDate=D'2024.01.01 00:00:01';
input ulong HistoryLength = 1000;
input bool Use_Returns = false;
input int DisplayDuration = 30;//display duration in seconds
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---download symbol data
   vector syA,syB;
   if(!syA.CopyRates(FirstSymbol,TimeFrame,COPY_RATES_CLOSE,StartDate,HistoryLength) ||
      !syB.CopyRates(SecondSymbol,TimeFrame,COPY_RATES_CLOSE,StartDate,HistoryLength) ||
      syA.Size()!=syB.Size())
     {
      Print("Error downloading price data ", ::GetLastError());
      return;
     }
//---optionally transform to returns
   if(Use_Returns)
     {
      syA = np::diff(log(syA));
      syB = np::diff(log(syB));
     }
//---create matrix container and fill it
   matrix xy(syA.Size(),2);
   if(!xy.Col(syA,0) || !xy.Col(syB,1))
     {
      Print(" column insertion error ", GetLastError());
      return ;
     }
//---build ECDF model
   CLinearCDF qd;
   if(!qd.fit(xy))
     {
      Print(" CLinearCDF failure ");
      return;
     }
//---transform raw values to quantiles
   matrix yx = qd.to_quantile(xy);
//---display QQ plot
   np::scatter_plot(yx.Col(0),yx.Col(1),"Q-Q plot",FirstSymbol,SecondSymbol,true,0,0,0,0,500,500,true,30);
//---
  }
//+------------------------------------------------------------------

ECDF_Demo.ex5スクリプトは、価格の二変量データセットにCLinearCDFクラスを適用することで、その使用方法を示しています。変換後の値は、データの分位点同士を比較するためのQ-Qプロットを表示するために使用されます。 

Q-Qプロット



コピュラの定義

コピュラとは、周辺分布とは独立して、確率変数間の依存構造を捉える多変量の累積分布関数です。より平易に言えば、コピュラは、各変数固有の挙動から変数間の依存関係を切り離して扱うことを可能にする数学的な仕組みです。

それぞれ独自の周辺分布を持つ複数の確率変数が与えられたとき、コピュラは、個々の分布形状に依存することなく、変数同士がどのように関係しているかを記述することで、これらの周辺分布を結合し、同時分布を構成します。このように、周辺分布と依存構造を分離できる点は、特に正規性や線形依存の仮定が適切でない場合において、現代の多変量解析の中核となる考え方です。この性質は、スコラーの定理(Sklar’s Theorem)によって数学的に定式化されています。スコラーの定理によれば、任意の連続な同時分布関数Fに対して、次の関係を満たす一意なコピュラCが存在します。

Copula

ここで、F₁(x₁)およびF₂(x₂)は、それぞれ確率変数X₁およびX₂の周辺累積分布関数です。

コピュラが2つ以上の確率変数間の依存関係を包含できる点は、相関係数のような連関の指標に例えることができます。ただし、相関係数が単一の数値に依存関係を要約するのに対し、コピュラは変数間の結びつきの性質について、より豊富な情報を提供します。

下側Fréchet–Hoeffding境界

上側Fréchet–Hoeffding境界

相関係数が−1から1の範囲に制約されているのと同様に、コピュラにもFréchet–Hoeffding境界と呼ばれる対応する上限および下限が存在します。下側および上側のFréchet–Hoeffding境界は、特定のコピュラが表現可能な最も負の依存関係および最も正の依存関係を定義する関数です。Fréchet–Hoeffding境界自体もコピュラであり、ここでのdは次元数、すなわち変数の個数を表します。


裾従属性

各コピュラ関数は固有の性質を持ち、それぞれ異なる種類の依存構造を表現できます。金融分野の応用において特に重要なコピュラの特性の一つが、裾従属性をモデル化する能力です。裾従属性とは、確率変数が分布の裾、すなわち極端な値の領域にあるときに、変数同士がどの程度同時に振る舞うかを測る指標です。コピュラが持つ裾従属性の種類によって、同時に発生する極端事象をどのように表現できるかが決まります。分布において、上側裾は非常に大きな値の領域を指し、下側裾は非常に小さな値の領域を指します。裾従属性は、裾従属性係数λによって定量化され、これは条件付き確率の極限として定義されます。裾従属性は、コピュラを用いて表現しない限り、定量的に評価することが困難な概念です。

下側裾従属性は、一方の変数が分布の下側裾に位置する、すなわち極端に小さい値を取っているときに、もう一方の変数も同様に下側裾に位置する確率を測定します。

下側裾従属性

上側裾従属性は、一方の変数が分布の上側裾に位置する、すなわち極端に大きい値を取っているときに、もう一方の変数も同様に上側裾に位置する確率を測定します。

上側裾従属性


コピュラの種類

コピュラは、その関数形を定義する広範なクラスやファミリーに分類できます。本連載で取り上げるコピュラは、いずれもパラメトリックコピュラです。これらは、捉える依存特性を定量化するパラメータによって特徴付けられるコピュラです。

楕円コピュラは、多変量正規分布(ガウス分布)や多変量スチューデントt分布などの楕円分布から導かれる多変量コピュラのクラスです。これらは主に、変数間の線形依存構造を捉える連関の指標によってパラメータ化されます。最も一般的な楕円コピュラは、正規コピュラとtコピュラです。次のセクションでは、二変量楕円コピュラの実装について詳しく説明し、まず二変量正規コピュラから取り上げます。



二変量正規コピュラ

二変量正規コピュラは、標準的な二変量正規分布から導かれます。このコピュラは、基礎となる二変量正規分布の線形相関係数ρという単一のパラメータによって特徴付けられます。このパラメータが、2つの確率変数間の依存の強さと方向を決定します。正規コピュラは放射対称であり、計算上扱いやすい点から広く利用されています。ただし、裾従属性は弱く、変数間で同時に極端な値が発生する場合を十分に捉えることはできません。

正規コピュラ

ヘッダファイルgaussian.mqhには、二変量正規コピュラを実装するCGaussianクラスが定義されています。CGaussianは、base.mqhで定義されている抽象クラスCBivariateCopulaを継承しており、以降で説明するすべての二変量コピュラの実装の基盤となっています。

//+------------------------------------------------------------------+
//| base copula class                                                |
//+------------------------------------------------------------------+
class CBivariateCopula:public CObject
  {
private:
   int               m_len;
protected:
   double            m_eps;
   double            m_theta;
   double            m_rho;
   double            m_nu;
   double            m_threshold;
   ENUM_COPULA_TYPE  m_copula_type;
   vector            m_bounds;
   double            m_tau;
   matrix            m_cov;
   virtual bool      check_theta(void) 
     {
       return (m_theta>m_bounds.Min() < m_theta < m_bounds.Max());  
     }
   virtual double    pdf(double u,double v) { return EMPTY_VALUE; }
   virtual double    cdf(double u,double v) { return EMPTY_VALUE; }
   virtual double    condi_cdf(double u,double v) { return EMPTY_VALUE; }
   virtual vector    pdf(vector& u,vector& v){ return vector::Zeros(0); }
   virtual vector    cdf(vector& u,vector& v){ return vector::Zeros(0); }
   virtual vector    condi_cdf(vector& u,vector& v){ return vector::Zeros(0); }
   virtual double    theta_hat(const double tau){ return EMPTY_VALUE; }
   void              preprocess(double &u, double &v)
     {
      u = MathMin(MathMax(m_eps,u),1.0-m_eps);
      v = MathMin(MathMax(m_eps,v),1.0-m_eps);
     }
   void              preprocess(vector &u, vector &v)
     {
      u.Clip(m_eps,1.0-m_eps);
      v.Clip(m_eps,1.0-m_eps);
     }
   double            kendalTau(const vector &vec1,const vector &vec2)
     {
      double tau = double("nan");
      ulong size=vec1.Size();
      if(size==0 || vec2.Size()!=size)
        {
         Print(__FUNCTION__, " size of input vectors donot match ");
         return(tau);
        }
      //---
      long cnt1=0,cnt2=0,cnt=0;
      //---
      for(long i=0; i<long(size); i++)
        {
         for(long j=i+1; j<long(size); j++)
           {
            double delta1=vec1[i]-vec1[j];
            double delta2=vec2[i]-vec2[j];
            double delta=delta1*delta2;
            if(delta==0)
              {
               if(delta1!=0)
                  cnt1++;
               if(delta2!=0)
                  cnt2++;
              }
            else
              {
               cnt1++;
               cnt2++;
               if(delta>0.0)
                  cnt++;
               else
                  cnt--;
              }
           }
        }
      //--- calculate Kendall tau
      long den=cnt1*cnt2;
      if(den==0)
        {
         Print(__FUNCTION__, " failed zero check at line 76");
         return tau;
        }
      tau=double(cnt)/MathSqrt(den);
      //---
      return(tau);
     }

public:
                     CBivariateCopula(void)
     {
      m_len = 6;
      m_eps = 1.e-5;
      m_theta = m_rho = m_nu = EMPTY_VALUE;
      m_copula_type = WRONG_VALUE;
      m_cov  = matrix::Zeros(0,0);
      m_bounds = vector::Zeros(2);
      m_bounds[0] = -DBL_MIN;
      m_bounds[1] = DBL_MAX;
     }
                    ~CBivariateCopula(void)
     {
     }
   double            Get_theta(void)  { return m_theta; }
   double            Get_tau(void)    { return m_tau;   }
   double            Get_rho(void)    { return m_rho;   }
   matrix            Get_covar(void)  { return m_cov;   }
   double            Get_nu(void)     { return m_nu;    }
   double            Get_threshold(void) { return m_threshold; }
   double            Get_eps(void)    { return m_eps;   }
   virtual void              Set_theta(double theta) {  m_theta = theta; }
   virtual void              Set_tau(double tau) { m_tau = tau; }
   virtual void              Set_threshold(double threshold) { m_threshold = threshold; }
   virtual void              Set_nu(double nu) { m_nu = nu; }
   virtual void              Set_rho(double rho) { m_rho = rho; }
   virtual void              Set_eps(double eps) { m_eps = eps; }
   virtual matrix            Sample(ulong num_samples) { return matrix::Zeros(0,0); }
   virtual void              Set_covariance(matrix &cov)
     {
      m_cov = cov;
      m_rho = m_cov[0][1] / (sqrt(m_cov[0][0]) * sqrt(m_cov[1][1]));
     }
   virtual double    Fit(vector &u, vector&v)
     {
      if(u.Max()>1.0 || v.Max()>1.0 || v.Min()<0.0 ||u.Min()<0.0)
       {
        Print(__FUNCTION__, " Invalid input variable(s) ");
        return EMPTY_VALUE;
       }
      m_tau = kendalTau(u,v);
      m_theta = theta_hat(m_tau);
      if(!check_theta())
        Print(__FUNCTION__, " Invalid theta " );
      return  m_theta;
     }
   double            Log_likelihood(vector &u, vector &v)
     {
      if(u.Size()!=v.Size())
        {
         Print(__FUNCTION__, " vectors are not of equal length ");
         return EMPTY_VALUE;
        }
      
      vector ll = pdf(u,v);

      return (log(ll)).Sum();
     }
   double            Copula_PDF(double u, double v)
     {
      preprocess(u,v);
      return pdf(u,v);
     }
   double            Copula_CDF(double u, double v)
     {
      preprocess(u,v);
      return cdf(u,v);
     }
   double            Conditional_Probability(double u, double v)
     {
      preprocess(u,v);
      return condi_cdf(u,v);
     }
   vector            Copula_PDF(vector& u, vector& v)
     {
      preprocess(u,v);
      return pdf(u,v);
     }
   vector            Copula_CDF(vector& u, vector& v)
     {
      preprocess(u,v);
      return cdf(u,v);
     }
   vector            Conditional_Probability(vector& u, vector& v)
     {
      preprocess(u,v);
      return condi_cdf(u,v);
     }
   double            Theta(double tau = 0.0)
     {
      if(!tau && m_tau)
         return theta_hat(m_tau);
      else
         if(tau)
            return theta_hat(tau);
         else
           {
            Print(__FUNCTION__ " invalid input parameter 'tau' ");
            return EMPTY_VALUE;
           }
     }

   virtual bool      Save(const int file_handle)
     {
      if(file_handle!=INVALID_HANDLE)
        {
         //---
         if(FileWriteLong(file_handle,-1)==sizeof(long))
           {
            //---
            if(FileWriteInteger(file_handle,int(m_copula_type),INT_VALUE)!=INT_VALUE)
               return(false);
            //---
            if(FileWriteInteger(file_handle,m_len,INT_VALUE)!=INT_VALUE)
               return(false);
            //---
            if(FileWriteDouble(file_handle,m_eps)!=sizeof(double) ||
               FileWriteDouble(file_handle,m_theta)!=sizeof(double) ||
               FileWriteDouble(file_handle,m_tau)!=sizeof(double) ||
               FileWriteDouble(file_handle,m_rho)!=sizeof(double) ||
               FileWriteDouble(file_handle,m_nu)!=sizeof(double) ||
               FileWriteDouble(file_handle,m_threshold)!=sizeof(double))
               return false;
            return true;
           }
        }
      return false;
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle!=INVALID_HANDLE)
        {
         //---
         if(FileReadLong(file_handle)==-1)
           {
            //---
            m_copula_type = ENUM_COPULA_TYPE(FileReadInteger(file_handle,INT_VALUE));

            if(FileReadInteger(file_handle,INT_VALUE)!=m_len)
               return false;

            ResetLastError();
            m_eps=FileReadDouble(file_handle);
            m_theta=FileReadDouble(file_handle);
            m_tau=FileReadDouble(file_handle);
            m_rho=FileReadDouble(file_handle);
            m_nu=FileReadDouble(file_handle);
            m_threshold=FileReadDouble(file_handle);
            if(GetLastError())
              {
               Print(__FUNCTION__, " possible read error ", GetLastError());
               return false;
              }
            return true;
           }
        }
      return false;
     }
   virtual int       Type(void)
     {
      return int(m_copula_type);
     }
  };

CGaussianクラスでは、コピュラモデルを2通りの方法で指定できます。1つは、アクセサメソッドを使用して関連するコピュラパラメータを明示的に設定する方法です。もう1つは、Fit()メソッドを使って二変量の一様分布データ(元の値の周辺CDFに変換したデータ)にモデルを適合させる方法です。適合に成功すると、EMPTY_VALUE定数以外の値が返されます。

コピュラの密度関数はCopula_PDF()メソッドで評価でき、Copula_CDF()メソッドではコピュラの同時累積分布関数を計算できます。条件付き累積分布関数はConditional_Probability()メソッドで求められます。これらのメソッドはすべて、スカラー入力およびベクトル入力に対応しています。Sample()メソッドを使用すると、コピュラのモデルパラメータに従った合成データセットを生成できます。さらに、Load()およびSave()メソッドを使うことで、コピュラモデルのシリアライズとデシリアライズによる永続化が可能です。

明示的に二変量正規コピュラを作成する場合は、2×2の共分散行列を指定する必要があります。

virtual void Set_covariance(matrix& cov) override
     {
      if(cov.Rows()!=2 || cov.Cols()!=2)
       {
        Print(__FUNCTION__," invalid input: expecting 2x2 matrix ");
        return;
       }
      m_cov = cov;
      m_rho = m_cov[0][1] / (sqrt(m_cov[0][0]) * sqrt(m_cov[1][1]));
      m_tau = (2.0/M_PI) * MathArcsin(m_rho);
      m_theta = theta_hat(m_tau);
     }

SampleEllipticCopula.ex5スクリプトは、楕円コピュラから生成した合成データが、指定したモデルパラメータに従っているかをテストします。ユーザーが設定可能な入力パラメータで、Covarで始まるものは共分散行列の各要素を指定します。Degree_Of_Freedomパラメータは、正規コピュラのテストには関係ありません。Sizeパラメータは、生成するサンプルの数を定義します。

//+------------------------------------------------------------------+
//|                                         SampleEllipticCopula.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<Copulas/Bivariate/gaussian.mqh>
#include<Copulas/Bivariate/t.mqh>
//+------------------------------------------------------------------+
//|  enum                                                            |
//+------------------------------------------------------------------+
enum ENUM_ELPT_COPULA
 {
  GAUSSIAN=0,//Gaussian
  STUDENT//Student
 };
//--- input parameters
input ENUM_ELPT_COPULA copula_to_sample_from = GAUSSIAN;
input double Covar_0_0_ = 2.0;
input double Covar_0_1_ = 0.5;
input double Covar_1_0_ = 0.5;
input double Covar_1_1_ = 2.0;
input double Degrees_Of_Freedom = 5.0;
input ulong Size = 1000;

プログラムでは、Sample()メソッドを使用して合成データを生成し、その後散布図として表示します。その後、標準正規分布の逆累積分布関数(逆CDF)をデータセットに適用し、相関行列を推定します。

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   matrix covar(2,2);
//---
   covar[0,0] = Covar_0_0_;
   covar[0,1] = Covar_0_1_;
   covar[1,0] = Covar_1_0_;
   covar[1,1] = Covar_1_1_;
//---
   CBivariateCopula *copula = NULL;
//---
   double set_theta = 0.0;
   switch(copula_to_sample_from)
    {
     case GAUSSIAN:
       copula = (CBivariateCopula*)new CGaussian();
       copula.Set_covariance(covar);
       break;
     case STUDENT:
       copula = (CBivariateCopula*)new CStudent();
       copula.Set_covariance(covar);
       copula.Set_nu(Degrees_Of_Freedom);
       break;
    }
//---
   Print("Specified correlation parameter of ", EnumToString(copula_to_sample_from)," copula :",copula.Get_rho());
   matrix samples = copula.Sample(Size);
   matrix inv_samples = samples;
   
   for(ulong i = 0; i<inv_samples.Rows(); ++i)
     for(ulong j =0; j<inv_samples.Cols(); ++j)
          inv_samples[i,j] = copula_to_sample_from == GAUSSIAN?CAlglib::InvNormalCDF(inv_samples[i,j]):studentTQuantile(inv_samples[i,j],Degrees_Of_Freedom);
          
   Print("Correlation of matrix of variable sampled from  ", EnumToString(copula_to_sample_from)," copula :\n", inv_samples.CorrCoef(false));      
   string cname = EnumToString((ENUM_COPULA_TYPE)copula.Type());
//---
   delete copula;
//---display scatter plot
   np::scatter_plot(inv_samples.Col(0),inv_samples.Col(1),cname+" Scatter plot","X","Y",true,0,0,0,0,500,300,true,10);  
//---
  }

ここでは、2回の実行結果を示します。期待される出力は、コピュラモデルの相関パラメータとほぼ同じ値の非対角要素を持つ行列です。指定するサンプル数によって結果は変わることに注意してください。サンプル数が多いほど、出力は指定した相関パラメータに近づきます。

CQ      0       14:06:16.963    SampleEllipticCopula (XAUEUR,D1)        Specified correlation parameter of GAUSSIAN copula :0.24999999999999994
DJ      0       14:06:17.021    SampleEllipticCopula (XAUEUR,D1)        Correlation of matrix of variable sampled from  GAUSSIAN copula :
DG      0       14:06:17.022    SampleEllipticCopula (XAUEUR,D1)        [[1,0.2497651923653427]
RK      0       14:06:17.022    SampleEllipticCopula (XAUEUR,D1)         [0.2497651923653427,1]]

次のセクションでは、他の楕円コピュラであるt-コピュラに注目します。


二変量tコピュラ

二変量tコピュラは、正規コピュラの代替としてよく用いられる手法の一つです。その理由は、正規コピュラでは捉えられない裾従属性を表現できるためです。tコピュラは、相関係数ρと自由度νの2つのパラメータによって定義されます。正規コピュラとは異なり、tコピュラは非ゼロの裾従属性を持ち、ある変数で極端な値が発生すると、もう一方の変数でも極端な値が生じやすくなります。

tコピュラ

ここで

  • tρ,ν​ :相関係数ρおよび自由度νを持つ二変量t分布の累積分布関数(CDF)
  • tν−1 :自由度νの一変量t分布の逆累積分布関数(分位関数)
  • u , v ∈ [ 0 , 1 ] u,v∈[0,1]

    tコピュラでは、裾従属性は分布の裾の方が中心よりも強くなります。これは、分布全体で均一な依存関係を持つ正規コピュラとは異なる特徴です。そのため、金融リターンのモデリングに特に適しており、同時に発生する暴落や急騰といった極端事象をより現実的に表現できます。自由度パラメータνが大きくなると、上側および下側の裾従属性は減少します。自由度が無限大に近づくと、tコピュラは正規コピュラに収束します。逆に、自由度が小さいほど裾従属性は強くなります。

    tコピュラは、t.mqhファイル内のCStudentクラスとして実装されており、CBivariateCopulaのサブクラスです。

    //+------------------------------------------------------------------+
    //|                                                            t.mqh |
    //|                                  Copyright 2025, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2025, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #include "base.mqh"
    #ifndef _NP_
    #include<np.mqh>
    #endif
    #include<Math/Stat/T.mqh>
    //+------------------------------------------------------------------+
    //|utilities for Student's t distribution: Cheng Algorithm           |
    //+------------------------------------------------------------------+
    double            chengFuAlgo(double probability,double df)
      {
       double k = ceil(df*0.5);
       double a = 1.0 - probability;
       double qi = EMPTY_VALUE;
       if(a!=0.5)
         {
          qi = sqrt(2.0*pow(1.0-2.0*a,2.0)/(1.0 - pow(1.0-2.0*a,2.0)));
          double i = 0.0;
          double gy,j;
          while(i<20.0)
            {
             gy = 0.0;
             j = 0.0;
             while(j<=k-1.0)
               {
                gy+=MathFactorial(int(2*j))/pow(2.0,2.0*j)/pow(MathFactorial(int(j)),2.0)*pow(1.0+pow(qi,2.0)/(2.0*k),-j);
                j+=1.0;
               }
             qi = 1.0/sqrt(1.0/(2.0*k)*(pow(gy/1.0-2.0*a,2.0) - 1.0));
             i+=1.0;
            }
          if(a>0.5)
             return -qi;
          else
             return qi;
         }
       else
          return 0.0;
      }
    //+------------------------------------------------------------------+
    //| Hills algorithm                                                  |
    //+------------------------------------------------------------------+
    double            hillsAlgo(double probability,double df)
      {
       double z,a,b,c,x,y,d;
       z = a = b = c = x = y = d = EMPTY_VALUE;
       bool neg = false;
       if(probability>0.5)
         {
          z = 2.0*(1.0-probability);
         }
       else
         {
          neg = true;
          z = 2.0*probability;
         }
       a = 1.0/(df - 0.5);
       b = 48.0/(a*a);
       c = ((20700.0*a/b - 98.0)*a - 16.0)*a + 96.36;
       d = ((94.5/(b + c) - 3.0)/b + 1.0)*sqrt(a*M_PI/2.0)*df;
       x = z*d;
       y = pow(x,(2.0/df));
    
       if(y>0.05 + a)
         {
          x = CAlglib::InvNormalCDF(z*0.5);
          y = x*x;
          if(df<5.0)
             c = c+0.3*(df-4.5)*(x+0.6);
          c = c + (((0.05*d*x - 5.0)*x - 7.0)*x - 2.0)*x + b;
          y = (((((0.4*y + 6.3)*y + 36.0)*y + 94.5)/c - y - 3.0)/b + 1.0)*x;
          y = a*y*y;
          if(y > 0.002)
             y = exp(y) - 1.0;
          else
             y = y + 0.5*y*y;
         }
       else
          y = ((1.0/(((df + 6.0)/(df*y) - 0.089*d - 0.822)*(df + 2.0)*3.0) + 0.5/(df + 4.0))*y - 1.0)*(df + 1.0)/(df + 2.0) + 1.0/y;
       double q = sqrt(df*y);
       return (neg)?-1.0*q:q;
      }
    //+------------------------------------------------------------------+
    //| Student's t Inverse CDF                                          |
    //+------------------------------------------------------------------+
    double            studentTQuantile(double probability, double df)
      {
       if(df == 1.0)
          return tan(M_PI*(probability-1.0/2.0));
       else
          if(df == 2.0)
             return (2.0*probability-1.0)*sqrt(2.0/(4.0*probability*(1.0-probability)));
          else
             if(df == 4.0)
               {
                double a = 4.0*probability*(1.0 - probability);
                double q = cos(1.0/3.0*acos(sqrt(a)))/sqrt(a);
                return np::sign(probability - 0.5)*2.0*sqrt(q-1.0);
               }
             else
                if(!MathMod(df,2.0))
                   return chengFuAlgo(probability,df);
                else
                   return hillsAlgo(probability,df);
      }
    //+------------------------------------------------------------------+
    //| Student T Probability Density Function                           |
    //+------------------------------------------------------------------+
    double studentTDensity(double x, double df)
      {
       return CAlglib::GammaFunction((df + 1.0)/2.0)/(sqrt(df*M_PI)*CAlglib::GammaFunction(df/2.0))*pow((1.0 + pow(x,2.0)/df),(-((df + 1.0)/2.0)));
      }
    //+------------------------------------------------------------------+
    //| Bivariate Student Copula                                         |
    //+------------------------------------------------------------------+
    class CStudent : public CBivariateCopula
      {
    private:
       virtual double    theta_hat(const double tau) override
         {
          return sin(tau*M_PI/2.0);
         }
       matrix            generate_corr_student(ulong num, matrix& cov, double nu_)
         {
          vector means = vector::Zeros(2);
          matrix normal = np::multivariate_normal(means,cov,num);
    
          double chi[];
          if(!MathRandomChiSquare(nu_,int(num),chi))
            {
             Print(__FUNCTION__, " Math random chisquare error ", GetLastError());
             return matrix::Zeros(0,0);
            }
    
          matrix out = matrix::Zeros(num,2);
    
          for(ulong i = 0; i<out.Rows(); i++)
            {
             double chisqrt = sqrt(chi[i]/nu_);
             out[i,0] = normal[i,0]/chisqrt;
             out[i,1] = normal[i,1]/chisqrt;
            }
          return out;
         }
       double            bvtdist(double z1,double z2, vector& mu, matrix& cov, double df)
         {
          double x1 = z1 - mu[0];
          double x2 = z2 - mu[1];
          double det_cov = cov[0,0]*cov[1,1]-cov[0,1]*cov[1,0];
    
          double xt = (-2.0*cov[0,1]*x1*x2+cov[0,0]*(pow(x1,2.0)+pow(x2,2.0)))/det_cov;
          double numerator = CAlglib::GammaFunction((2.0+df)/2.0);
          double denominator = (CAlglib::GammaFunction(df/2.0)*df*M_PI*sqrt(det_cov)*pow(1.0+xt/df,(2.0+df)/2.0));
          return numerator/denominator;
         }
       //+------------------------------------------------------------------+
       //|  inner integral function object                                  |
       //+------------------------------------------------------------------+
       class CInnerInt : public CIntegrator1_Func
         {
       private:
          double            m_x;
          double            m_rho;
          matrix            m_corr;
          vector            m_mu;
          double            m_nu;
       public:
          //---
                         CInnerInt(void)
            {
             m_mu = vector::Zeros(2);
            }
                        ~CInnerInt(void) {}
          void              Set(double x, double rho, double nu)
            {
             m_x = x;
             m_nu = nu;
             m_rho = rho;
             matrix crr = {{1.0, m_rho}, {m_rho, 1.0}};
             m_corr = crr;
            }
          virtual void      Int_Func(double x,double xminusa,double bminusx,double &y,CObject &obj)
            {
             double x1 = m_x - 0.0;
             double x2 = x - 0.0;
             double det_cov = m_corr[0,0]*m_corr[1,1]-m_corr[0,1]*m_corr[1,0];
    
             double xt = (-2.0*m_corr[0,1]*x1*x2+m_corr[0,0]*(pow(x1,2.0)+pow(x2,2.0)))/det_cov;
             double numerator = CAlglib::GammaFunction((2.0+m_nu)/2.0);
             double denominator = (CAlglib::GammaFunction(m_nu/2.0)*m_nu*M_PI*sqrt(det_cov)*pow(1.0+xt/m_nu,(2.0+m_nu)/2.0));
             y =  numerator/denominator;
            }
         };
       //+------------------------------------------------------------------+
       //|  outer integral function object                                  |
       //+------------------------------------------------------------------+
       class COuterInt : public CIntegrator1_Func
         {
       public:
          double            ay_limit;
          double            by_limit;
          double            nu;
          double            rho;
          //---
                         COuterInt(void) {}
                        ~COuterInt(void) {}
          virtual void      Int_Func(double x,double xminusa,double bminusx,double &y,CObject &obj)
            {
             CInnerInt fint;
             //---
             fint.Set(x,rho,nu);
             //---
             CAutoGKStateShell s;
             //---
             double integral;
             //---
             CObject ob;
             //---
             CAutoGKReportShell rep;
             //---
             CAlglib::AutoGKSmooth(ay_limit,by_limit,s);
             //---
             CAlglib::AutoGKIntegrate(s,fint,ob);
             //---
             CAlglib::AutoGKResults(s,integral,rep);
             //---
             CAutoGKReport report = rep.GetInnerObj();
             //---
             if(report.m_terminationtype<0.0)
                Print(__FUNCTION__, " integration error ",report.m_terminationtype);
             //---
             y = integral;
            }
         };
    protected:
       virtual double    pdf(double u,double v) override
         {
          double y1,y2;
          y1 = studentTQuantile(u,m_nu);
          y2 = studentTQuantile(v,m_nu);
          vector means = vector::Zeros(2);
          matrix corr = {{1.0,m_rho},{m_rho,1.0}};
          double numerator = bvtdist(y1,y2,means,corr,m_nu);
          double denominator,pdf1,pdf2;
          pdf1 = studentTDensity(y1,m_nu);
          pdf2 = studentTDensity(y2,m_nu);
          denominator = pdf1*pdf2;
          return numerator/denominator;
         }
       virtual double    cdf(double u,double v) override
         {
          double uu,vv;
          uu = MathMax(u,1.e-6);
          vv = MathMax(v,1.e-6);
          //---
          int errcode = 0;
          //---
          COuterInt fout;
          fout.rho = m_rho;
          fout.nu = m_nu;
          fout.ay_limit = double(LONG_MIN);
          fout.by_limit = studentTQuantile(vv,m_nu);
          double ax_limit = double(LONG_MIN);
          double bx_limit = studentTQuantile(uu,m_nu);
          //---
          CObject obj;
          CAutoGKStateShell ss;
          //---
          double outer_integral;
          CAutoGKReportShell repp;
          //---
          CAlglib::AutoGKSmooth(ax_limit,bx_limit,ss);
          //---
          CAlglib::AutoGKIntegrate(ss,fout,obj);
          //---
          CAlglib::AutoGKResults(ss,outer_integral,repp);
          //---
          CAutoGKReport report = repp.GetInnerObj();
          //---
          if(report.m_terminationtype<0.0)
             Print(__FUNCTION__, " integration error ",report.m_terminationtype);
          //---
          return MathMax(MathMin(outer_integral,1.0),0.0);
         }
       virtual double    condi_cdf(double u,double v) override
         {
          double inv_u,inv_v;
          int errcode = 0;
          inv_u = studentTQuantile(u,m_nu);
          inv_v = studentTQuantile(v,m_nu);
          double numerator,denominator;
          numerator = (inv_u-m_rho*inv_v)*sqrt(m_nu+1.0);
          denominator = sqrt((1.0-pow(m_rho,2.))*(pow(inv_v,2.0)+m_nu));
          double tcdf = MathCumulativeDistributionT(numerator/denominator,m_nu+1.0,errcode);
          if(errcode)
            {
             Print(__FUNCTION__, " mathcdf error ", errcode);
             return EMPTY_VALUE;
            }
          return tcdf;
         }
       virtual vector    pdf(vector &u,vector &v) override
         {
          vector out(u.Size());
          for(ulong i = 0; i<u.Size(); ++i)
             out[i] = pdf(u[i],v[i]);
          return out;
         }
       virtual vector    cdf(vector &u,vector &v) override
         {
          vector out(u.Size());
          for(ulong i = 0; i<u.Size(); ++i)
             out[i] = cdf(u[i],v[i]);
          return out;
         }
       virtual vector    condi_cdf(vector &u,vector &v) override
         {
          vector out(u.Size());
          for(ulong i = 0; i<u.Size(); ++i)
             out[i] = condi_cdf(u[i],v[i]);
          return out;
         }
    public:
                         CStudent(void)
         {
          m_copula_type = STUDENT_COPULA;
         }
                        ~CStudent(void)
         {
         }
    
       virtual matrix    Sample(ulong num_samples) override
         {
          matrix spairs = generate_corr_student(num_samples,m_cov,m_nu);
          matrix out = spairs;
          int err1,err2;
          err1 = err2 = 0;
          for(ulong i = 0; i<spairs.Rows(); ++i)
            {
             out[i,0] = MathCumulativeDistributionT(spairs[i,0],m_nu,err1);
             out[i,1] = MathCumulativeDistributionT(spairs[i,1],m_nu,err2);
             if(err1 || err2)
               {
                Print(__FUNCTION__, " mathcdf error ", err1?err1:err2);
                return matrix::Zeros(0,0);
               }
            }
          return out;
         }
       virtual double    Fit(vector &u, vector&v) override
         {
          if(u.Max()>1.0 || v.Max()>1.0 || v.Min()<0.0 ||u.Min()<0.0)
            {
             Print(__FUNCTION__, " Invalid input variable(s) ");
             return EMPTY_VALUE;
            }
          m_tau = kendalTau(u,v);
          m_theta = theta_hat(m_tau);
          if(m_nu == EMPTY_VALUE)
             m_nu = fit_nu(u,v);
          matrix vals = matrix::Zeros(u.Size(),2);
          for(ulong i = 0; i<vals.Rows(); ++i)
            {
             vals[i,0] = studentTQuantile(u[i],m_nu);
             vals[i,1] = studentTQuantile(v[i],m_nu);
            }
          m_cov = vals.Cov(false);
          m_rho = m_cov[0,1]/(sqrt(m_cov[0,0])*sqrt(m_cov[1,1]));
          return m_rho;
         }
      };
    
    //+------------------------------------------------------------------+
    


    以下は、SampleEllipticCopula.ex5を実行し、tコピュラオプションを選択した場合の2回の実行結果です。

    IM      0       14:07:00.097    SampleEllipticCopula (XAUEUR,D1)        Specified correlation parameter of STUDENT copula :0.24999999999999994
    NQ      0       14:07:00.295    SampleEllipticCopula (XAUEUR,D1)        Correlation of matrix of variable sampled from  STUDENT copula :
    GF      0       14:07:00.295    SampleEllipticCopula (XAUEUR,D1)        [[1,0.2441795870726779]
    MJ      0       14:07:00.295    SampleEllipticCopula (XAUEUR,D1)         [0.2441795870726779,1]]


    テストと検証

    両方のコピュラ実装の残りのメソッドをテストするために、TestBivariateGaussianCopula.ex5およびTestBivariateStudentTCopula.ex5スクリプトを用意しています。これらのスクリプトでは、特定のコピュラモデルを作成し、PDF、CDF、および条件付きCDFのコードを評価します。これらのテストは、MQL5コードの元となったArbitrageLab Pythonパッケージのユニットテストを再現したものです。これにより、MQL5コードが元のPython実装と同じ方法で動作するかどうかを確認できます。

    //+------------------------------------------------------------------+
    //|                                  TestBivariateGaussianCopula.mq5 |
    //|                                  Copyright 2025, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2025, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- input parameters
    #include<Copulas/Bivariate/gaussian.mqh>
    //---
     double Covar_0_0_ = 2.0;
     double Covar_0_1_ = 0.5;
     double Covar_1_0_ = 0.5;
     double Covar_1_1_ = 2.0;
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //---
       matrix covar(2,2);
    //---
       covar[0,0] = Covar_0_0_;
       covar[0,1] = Covar_0_1_;
       covar[1,0] = Covar_1_0_;
       covar[1,1] = Covar_1_1_;
    //---   
       CGaussian norm_copula;
       norm_copula.Set_covariance(covar);
    //---   
       Print("Rho ", norm_copula.Get_rho());
       Print("Joint cdf(0.7,0.0001) ",norm_copula.Copula_CDF(0.7,0.0001));
       Print("Joint cdf(0.0001,0.7) ",norm_copula.Copula_CDF(0.0001,0.7));
       Print("Joint cdf(0.7,1.0) ",norm_copula.Copula_CDF(0.7,1.0));
       Print("Joint cdf(0.5,0.7) ",norm_copula.Copula_CDF(0.5,0.7));
    //---   
       Print("Joint pdf(0.5,0.7) ",norm_copula.Copula_PDF(0.5,0.7));
       Print("Joint pdf(0.7,0.5) ",norm_copula.Copula_PDF(0.7,0.5));
       Print("Joint pdf(0.6,0.7) ",norm_copula.Copula_PDF(0.6,0.7));
    //---   
       Print("Conditional CDF (U<=0.5|V=0.7) ",norm_copula.Conditional_Probability(0.5,0.7));
      }
    //+------------------------------------------------------------------+
    

    CGaussianメソッドのテストの結果は次のとおりです。

    KR      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Rho 0.24999999999999994
    JR      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint cdf(0.7,0.0001) 0.00009407442939459683
    JG      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint cdf(0.0001,0.7) 0.00009407442939459683
    KK      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint cdf(0.7,1.0) 0.6999973036455064
    FL      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint cdf(0.5,0.7) 0.38494413131861455
    IR      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint pdf(0.5,0.7) 1.0233716657780745
    KG      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint pdf(0.7,0.5) 1.0233716657780745
    LK      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Joint pdf(0.6,0.7) 1.0580116369285744
    EP      0       14:40:26.403    TestBivariateGaussianCopula (XAUEUR,D1) Conditional CDF (U<=0.5|V=0.7) 0.4461479582632463

    比較のために、正規コピュラに関する単体テストのPythonコードを次に示します。

    def test_gaussian(self):
            """
            Test Gaussian copula class.
            """
    
            cov = [[2, 0.5], [0.5, 2]]
            cop = GaussianCopula(cov=cov)
            # Check describe
            descr = cop.describe()
            self.assertEqual(descr['Descriptive Name'], 'Bivariate Gaussian Copula')
            self.assertEqual(descr['Class Name'], 'Gaussian')
            self.assertEqual(descr['cov'], cov)
            self.assertAlmostEqual(descr['rho'], 0.25, delta=1e-5)
    
            # Check copula joint cumulative density C(U=u,V=v)
            self.assertAlmostEqual(cop.C(0.7, 1e-4), cop.C(1e-4, 0.7), delta=1e-4)
            self.assertAlmostEqual(cop.C(0.7, 1), cop.C(1, 0.7), delta=1e-4)
            self.assertAlmostEqual(cop.C(0.7, 1e-8), 0, delta=1e-4)
            self.assertAlmostEqual(cop.C(0.7, 1), 0.7, delta=1e-4)
            self.assertAlmostEqual(cop.C(0.5, 0.7), 0.384944, delta=1e-4)
    
            # Check copula joint probability density c(U=u,V=v)
            self.assertAlmostEqual(cop.c(0.5, 0.7), cop.c(0.7, 0.5), delta=1e-8)
            self.assertAlmostEqual(cop.c(0.5, 0.7), 1.023371665778, delta=1e-4)
            self.assertAlmostEqual(cop.c(0.6, 0.7), 1.058011636928, delta=1e-4)
    
            # Check copula conditional cdf Prob(U<=u|V=v)
            self.assertAlmostEqual(cop.condi_cdf(0.5, 0.7), 0.446148, delta=1e-4

    次に、CStudentクラスの結果を示します。

    ER      0       14:40:34.138    TestBivariateStudentTCopula (XAUEUR,D1) Rho 0.24999999999999994
    JG      0       14:40:35.369    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(0.0,0.2) 0.000006839679521344817
    JD      0       14:40:36.613    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(0.2,0.0) 0.000006839679521344818
    HK      0       14:40:38.041    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(0.7,1.0) 0.6999969941893684
    PO      0       14:40:39.584    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(1.0,0.7) 0.6999969941893683
    OR      0       14:40:41.280    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(1.0,1.0) 0.9999810861796908
    RF      0       14:40:42.452    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(0.0,0.0) 0.0000010861794979038194
    HJ      0       14:40:43.757    TestBivariateStudentTCopula (XAUEUR,D1) Joint cdf(0.3,0.7) 0.23534922600994124
    DL      0       14:40:43.757    TestBivariateStudentTCopula (XAUEUR,D1) Joint pdf(0.5,0.7) 1.0915055449624917
    FP      0       14:40:43.757    TestBivariateStudentTCopula (XAUEUR,D1) Joint pdf(0.7,0.5) 1.0915055449624917
    MD      0       14:40:43.757    TestBivariateStudentTCopula (XAUEUR,D1) Joint pdf(0.6,0.7) 1.1416004955531887
    DD      0       14:40:43.757    TestBivariateStudentTCopula (XAUEUR,D1) Conditional CDF (U<=0.5|V=0.7) 0.441518430406228
    MG      0       14:40:43.757    TestBivariateStudentTCopula (XAUEUR,D1) Loglikelihood 2.135711666815534

    tコピュラの単体テストのPythonコードを次に示します。

    def test_student(self):
            """
            Test Student copula class (Student-t).
            """
    
            cov = [[2, 0.5], [0.5, 2]]
            nu = 5
            cop = StudentCopula(cov=cov, nu=nu)
            # Check describe
            descr = cop.describe()
            self.assertEqual(descr['Descriptive Name'], 'Bivariate Student-t Copula')
            self.assertEqual(descr['Class Name'], 'Student')
            self.assertEqual(descr['cov'], cov)
            self.assertEqual(descr['nu (degrees of freedom)'], nu)
            self.assertAlmostEqual(descr['rho'], 0.25, delta=1e-5)
            # More to be added here for test on C(U<=u, V<=v)
    
            # Check copula joint probability density c(U=u,V=v)
            self.assertAlmostEqual(cop.c(0.5, 0.7), cop.c(0.7, 0.5), delta=1e-8)
            self.assertAlmostEqual(cop.c(0.5, 0.7), 1.09150554, delta=1e-4)
            self.assertAlmostEqual(cop.c(0.6, 0.7), 1.1416005, delta=1e-4)
    
            # Check copula joint cumulative density C(U=u,V=v)
            self.assertAlmostEqual(cop.C(0, 0.2), cop.C(0.2, 0), delta=1e-4)
            self.assertAlmostEqual(cop.C(0.7, 1), cop.C(1, 0.7), delta=1e-4)
            self.assertAlmostEqual(cop.C(1, 1), 1, delta=1e-4)
            self.assertAlmostEqual(cop.C(0, 0), 0, delta=1e-4)
            self.assertAlmostEqual(cop.C(0.3, 0.7), 0.23534923332657925, delta=1e-4)
    
            # Check copula conditional cdf Prob(U<=u|V=v)
            self.assertAlmostEqual(cop.condi_cdf(0.5, 0.7), 0.4415184293094455, delta=1e-4)
    
            # Check theta(tau)
            self.assertAlmostEqual(cop.theta_hat(2 * np.arcsin(0.2) / np.pi), 0.2, delta=1e-4)
    
            # log_ml function in ccalc
            u = np.array([0.1, 0.21, 0.5, 0.8])
            v = np.array([0.01, 0.25, 0.4, 0.7])
            new_cop = StudentCopula(nu=5)
            new_cop.fit(u, v)
            ll = new_cop.get_log_likelihood_sum(u=u,
                                                v=v)
            self.assertAlmostEqual(ll, 2.1357117471178584, delta=1e-5

    出力からわかるように、ライブラリは正常に機能しているようです。


    結論

    これまでに実装した楕円コピュラを比較すると、tコピュラは同時に発生する極端事象のモデリングにより適していると言えます。ただし、非対称な裾従属性、すなわち一方の裾で依存が強く、もう一方では弱い場合を捉えることはできません。そのような場合には、クレイトンやグンベルのようなアルキメデスコピュラの方が有用です。

    次回の記事では、アルキメデスコピュラとそのMQL5での実装について解説します。十分な数のコピュラが実装できた段階で、コピュラをペア取引への応用にどのように活かすかについても見ていきます。


    ファイルまたはフォルダ 詳細 
    MQL5/include/np.mqh 各種ベクトルおよび行列のユーティリティ関数をまとめたヘッダファイル
    MQL5/include/Copulas/Bivariate 二変量コピュラの実装に関するすべてのヘッダファイルが含まれるフォルダ 
    MQL5/include/ECDF 記事で解説した経験累積分布関数(ECDF)実装用ヘッダファイルが含まれるフォルダ
    MQL5/script/ECDF_Demo.mq5 記事で解説したECDFの実装を適用するデモ用スクリプト
    MQL5/script/TestBivariateGaussianCopula.mq5 CGaussianクラスの使用方法を示すテストスクリプト
    MQL5/script/TestBivariateStudentTCopula.mq5 CStudentクラスの使用方法を示すテストスクリプト
    MQL5/script/SampleEllipticCopula.mq5 楕円コピュラからのサンプリングを示すスクリプト 

    MetaQuotes Ltdにより英語から翻訳されました。
    元の記事: https://www.mql5.com/en/articles/18361

    添付されたファイル |
    MQL5入門(第24回):チャートオブジェクトで取引するEAの構築 MQL5入門(第24回):チャートオブジェクトで取引するEAの構築
    本記事では、チャート上に描かれたサポートラインやレジスタンスラインを検出し、それに基づいて自動で取引を実行するエキスパートアドバイザー(EA)の作成方法を解説します。
    MQL5入門(第23回):オープニングレンジブレイクアウト戦略の自動化 MQL5入門(第23回):オープニングレンジブレイクアウト戦略の自動化
    この記事では、MQL5でオープニングレンジブレイクアウト(ORB)エキスパートアドバイザー(EA)を作成する方法を解説します。EAが市場の初期レンジからのブレイクアウトをどのように検知し、それに応じてポジションを建てるかを説明します。また、建てるポジションの数を制御したり、指定した時間で自動的に取引を停止する方法についても学べます。
    知っておくべきMQL5ウィザードのテクニック(第84回):ストキャスティクスとFrAMAのパターンの使用 - 結論 知っておくべきMQL5ウィザードのテクニック(第84回):ストキャスティクスとFrAMAのパターンの使用 - 結論
    ストキャスティクスとフラクタル適応型移動平均(FrAMA: Fractal Adaptive Moving Average)は、互いに補完し合う特性を持っており、MQL5のエキスパートアドバイザー(EA)で使えるインジケーターペアの1つです。この組合せについては前回の記事で紹介しましたが、今回はその締めくくりとして、残る5つのシグナルパターンを検討していきます。これらの検証にあたっては、これまでと同様にMQL5ウィザードを用いて構築およびテストをおこないます。
    プライスアクション分析ツールキットの開発(第45回):MQL5で動的水準分析パネルを作成する プライスアクション分析ツールキットの開発(第45回):MQL5で動的水準分析パネルを作成する
    この記事では、ワンクリックで任意の価格水準をテストできる強力なMQL5ツールについて説明します。テストしたい価格を入力して分析ボタンを押すと、EAは過去のデータを瞬時にスキャンし、チャート上でその水準に触れた箇所やブレイクアウトをハイライト表示します。また、統計情報を整理されたダッシュボードに表示し、価格がその水準にどの程度反応したか、ブレイクしたか、サポートとして機能したか、レジスタンスとして働いたかを一目で確認できます。以下では、詳細な手順について解説します。