English 日本語
preview
Bivariate Copulae in MQL5 (Teil 2): Implementierung archimedischer Copulae in MQL5

Bivariate Copulae in MQL5 (Teil 2): Implementierung archimedischer Copulae in MQL5

MetaTrader 5Beispiele |
17 0
Francis Dube
Francis Dube

Einführung

Im vorigen Artikel haben wir die MQL5-Implementierungen bivariater elliptischer Copulae vorgestellt, insbesondere die Gaußsche Copula und die Studentsche t-Copula. Bei der Anwendung dieser Copulae gibt es mindestens zwei Nachteile: Ihre algebraische Darstellung ist kompliziert, und ihr Symmetriegrad kann problematisch sein. Diese Probleme haben den Anstoß für die Suche nach Copulae mit geeigneten algebraischen Ausdrücken gegeben, die auch asymmetrische Abhängigkeiten in Datensätzen modellieren können. Eine der beliebtesten Familien von Copulae, die beide Anforderungen erfüllen, ist die Klasse der archimedischen Copulae. Daher werden in diesem Artikel gängige bivariate archimedische Copulae und ihre Implementierung in MQL5 diskutiert. Die Bibliothek der bivariaten Copulas wird um Implementierungen von Frank-, Joe-, Gumbel-, Clayton-, N13- und N14-Copulas erweitert.


Einführung archimedischer Copulae

Eine bivariate, archimedische Copula ist ein spezieller Typ einer Copula C(u,v), die in der Statistik zur Modellierung der Abhängigkeitsstruktur zwischen zwei Zufallsvariablen mit gleichmäßigen Randverteilungen verwendet wird. Ihre bestimmende Eigenschaft ist ihre generative Form, die durch eine einzige, stetige, streng abnehmende und konvexe Funktion, die Generatorfunktion ϕ, ausgedrückt werden kann.

Generator-Funktion

Der Generator ϕ muss ϕ(1)=0 erfüllen. Diese Struktur verleiht den archimedischen Copulae einen hohen Grad an Symmetrie (C(u,v)=C(v,u)) und ermöglicht die Modellierung eines breiten Spektrums von Abhängigkeitsstrukturen durch die Wahl verschiedener Generatorfunktionen. Archimedische Copulae sind grundsätzlich flexibler als gewöhnliche Copulae, aber diese Flexibilität wird in der Regel für den praktischen Gebrauch eingeschränkt. Theoretisch gibt es eine unendliche Anzahl von Möglichkeiten für die Generatorfunktion. Jede kleine Änderung der Funktion erzeugt eine neue, einzigartige Copula. In der Praxis wählt man in der Regel eine parametrische Familie, die von einer einzigen Generatorfunktion gesteuert wird, die durch eine einzige skalare Eingangsvariable definiert ist. Dies erleichtert die Schätzung und Modellierung erheblich. Daher sind die meisten gängigen archimedischen Familien durch einen einzigen, in die Generatorfunktion eingebetteten Parameter gekennzeichnet, der die Stärke der Abhängigkeit bestimmt.

Die Generatorfunktion definiert eine archimedische Copula und erfasst die gesamte Abhängigkeitsstruktur zwischen den Zufallsvariablen. Vereinfacht ausgedrückt, besteht seine Aufgabe darin, die Randwahrscheinlichkeiten in eine Abhängigkeitsstruktur zu übersetzen, die durch einfache Addition leicht kombiniert werden kann. Die Funktion transformiert die Randvariable. Da der Generator streng absteigend ist, werden niedrige Wahrscheinlichkeiten auf große Zahlen und hohe Wahrscheinlichkeiten auf 0 abgebildet. Dies ist eine Umkehrung der Wahrscheinlichkeitsskala. Die spezifische Form und die Parameter der Generatorfunktion bestimmen vollständig die resultierende Copula-Familie und damit die genaue Art und Weise, wie die beiden Variablen voneinander abhängen. Im nächsten Abschnitt beginnen wir unsere Erkundung der archimedischen Copula mit der bivariaten Frank-Copula.


Bivariate Frank-Copula

Die bivariate Frank-Copula ist eine symmetrische archimedische Copula, die sowohl positive als auch negative Abhängigkeiten modellieren kann, ohne eine Randabhängigkeit aufzuweisen. Sie wird durch die Generatorfunktion definiert:

Franken-Generator Funktion

wobei θ (theta) die Stärke und Richtung der Assoziation bestimmt. θ-Werte von Null sind für den Generator der Frank-Copula undefiniert. Positive Werte von θ weisen auf eine positive Abhängigkeit hin, während negative Werte einer negativen Abhängigkeit entsprechen; wenn θ gegen 0 tendiert, nähert sich die Copula der Unabhängigkeits-Copula an. Die Frank-Copula ist radialsymmetrisch, d. h. sie behandelt beide Enden gleich und weist weder eine Abhängigkeit vom oberen noch vom unteren Randes auf. Dadurch eignet es sich gut für die Modellierung moderater Abhängigkeitsstrukturen, die symmetrisch um das Zentrum der Verteilung sind, wie z. B. bei Anwendungen mit kontinuierlichen Variablen, bei denen extreme Ko-Bewegungen unwahrscheinlich sind.

Die Klasse CFrank, definiert in frank.mqh, erbt von CBivariateCopula, um eine Frank-Copula darzustellen.

//+------------------------------------------------------------------+
//|                                                        frank.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "base.mqh"
//+------------------------------------------------------------------+
//| Frank Copula.                                                    |
//+------------------------------------------------------------------+
class CFrank:public CBivariateCopula
  {
private:
   class CInt_Function_1_Func : public CIntegrator1_Func
     {
   private:
      double         m_th;
   public:
      //---
                     CInt_Function_1_Func(void) {}
                    ~CInt_Function_1_Func(void) {}
      void              set_theta(double theta)
        {
         m_th = theta;
        }
      virtual void      Int_Func(double x,double xminusa,double bminusx,double &y,CObject &obj)
        {
         y = x/m_th/(exp(x)-1.0);
        }
     };
   class CBrent1:public CBrentQ
     {
   private:
      double            debyel(double theta)
        {
         CInt_Function_1_Func fint;
         fint.set_theta(theta);
         CObject obj;
         CAutoGKStateShell s;
         double integral;
         CAutoGKReportShell rep;
         //---
         CAlglib::AutoGKSmooth(0.0,theta,s);
         //---
         CAlglib::AutoGKIntegrate(s,fint,obj);
         //---
         CAlglib::AutoGKResults(s,integral,rep);

         CAutoGKReport report = rep.GetInnerObj();

         if(report.m_terminationtype<0.0)
            Print(__FUNCTION__, " integration error ",report.m_terminationtype);

         return integral;
        }
   public:
                     CBrent1(void)
        {
        }
                    ~CBrent1(void)
        {
        }
      virtual double objective(double u, double v,double y)
        {
         return (1.0 - 4.0/u+4.0*debyel(u)/u) - y;
        }
     };

   virtual double    theta_hat(const double tau) override
     {
      CBrent1 fun;
      return fun.minimize(0,tau,-100.0,100.0,2.e-12,4.0*2.e-16,100);
     }
   vector            generate_pair(double v1, double v2)
     {
      vector out(2);
      out[0] = v1;
      out[1] = -1.0/m_theta*log(1.0+(v2*(1.0-exp(-m_theta)))/(v2*(exp(-m_theta*v1)-1.0)-exp(-m_theta*v1)));
      return out;
     }
protected:
   virtual double    pdf(double u,double v) override
     {
      double et,eut,evt,pd;
      et = exp(m_theta);
      eut = exp(u*m_theta);
      evt = exp(v*m_theta);
      pd = et*eut*evt*(et - 1.0)*m_theta/pow(et+eut*evt-et*eut-et*evt,2.0);
      return pd;
     }
   virtual double    cdf(double u, double v) override
     {
      return (-1.0 / m_theta * log(1.0 + (exp(-1.0 * m_theta * u) - 1.0) * (exp(-1.0 * m_theta * v) - 1.0)/ (exp(-1 * m_theta) - 1.0)));
     }
   virtual double    condi_cdf(double u, double v) override
     {
      double enut = exp(-u*m_theta);
      double envt = exp(-v*m_theta);
      double ent = exp(-m_theta);
      double denominator = ((ent - 1.0) + (enut - 1.0) * (envt - 1.0));
      if(denominator)
         return (envt * (enut - 1.0)/ (denominator));
      else
         return EMPTY_VALUE;
     }
   virtual vector    pdf(vector &u,vector &v) override
     {
      vector eut,evt,pd;
      double et = exp(m_theta);
      eut = exp(u*m_theta);
      evt = exp(v*m_theta);
      pd = et*eut*evt*(et - 1.0)*m_theta/pow(et+eut*evt-et*eut-et*evt,2.0);
      return pd;
     }
   virtual vector    cdf(vector &u, vector &v) override
     {
      return (-1.0 / m_theta * log(1.0 + (exp(-1.0 * m_theta * u) - 1.0) * (exp(-1.0 * m_theta * v) - 1.0)/ (exp(-1 * m_theta) - 1.0)));
     }
   virtual vector    condi_cdf(vector &u, vector &v) override
     {
      vector enut = exp(-1.0*u*m_theta);
      vector envt = exp(-1.0*v*m_theta);
      double ent = exp(-m_theta);

      return (envt * (enut - 1.0)/ ((ent - 1.0) + (enut - 1.0) * (envt - 1.0)));
     }
public:
                     CFrank(void)
     {
      m_threshold = 1.e-10;
      m_copula_type = FRANK_COPULA;
      m_invalid_theta = 0.0;
     }
                    ~CFrank(void)
     {
     }
   virtual matrix    Sample(ulong num_samples) override
     {
      double unf_v[],unf_c[];

      vector v,c,u;

      if(!MathRandomUniform(0.0,1.0,int(num_samples),unf_v) ||
         !MathRandomUniform(0.0,1.0,int(num_samples),unf_c) ||
         !v.Assign(unf_v) || !c.Assign(unf_c))
        {
         Print(__FUNCTION__, " failed to get uniform random numbers ", GetLastError());
         return matrix::Zeros(0,0);
        }

      matrix out(num_samples,2);

      for(ulong irow = 0; irow<num_samples; irow++)
         out.Row(generate_pair(v[irow],c[irow]),irow);

      return out;

     }

  };
//+------------------------------------------------------------------+

Der Parameter θ wird anhand von Datenstichproben durch Schätzung der Rangkorrelation τ (Tau von Kendall) unter Verwendung der folgenden Beziehung bestimmt:

Das Verhältnis von Theta und dem Tau von Kendall


wobei D1(θ) ist die Debye-Funktion der Ordnung 1. Unten sehen Sie ein Streudiagramm eines synthetischen Datensatzes, der aus einer Frank-Copula mit dem Theta-Parameter 5 abgerufen wurde.

Frank Copula Muster


Bivariate Clayton-Copula

Als Nächstes folgt die bivariate Clayton-Copula, die sich durch ihre Fähigkeit auszeichnet, eine starke Abhängigkeit im unteren Bereich und eine Asymmetrie zwischen den Enden einer gemeinsamen Verteilung zu modellieren. Sie wird durch die Generatorfunktion definiert:

Clayton-Generator-Funktion

Je größer der Wert von θ ist, desto stärker ist die Assoziation im unteren Bereich. Die Clayton-Copula weist eine Abhängigkeit mit dem Koeffizienten im unteren Bereich auf:

Clyton-Rand-Abhängigkeit

Sie ist nicht von der Länge des oberen Rands abhängig (λU=0) und eignet sich daher besonders gut für die Modellierung von Situationen, in denen extrem niedrige Werte häufig zusammen auftreten. Sie erfasst nur positive Abhängigkeiten (θ>0) und nähert sich der Unabhängigkeits-Copula an, wenn die θ-Werte gegen 0 gehen. Beachten Sie, dass die Clayton-Copula für θ=0 undefiniert ist. Aufgrund seiner asymmetrischen Natur kann er Beziehungen darstellen, bei denen gemeinsame Abschwünge wahrscheinlicher sind als gemeinsame Aufschwünge. Die Header-Datei clayton.mqh enthält die Definition der Klasse CClayton.

//+------------------------------------------------------------------+
//|                                                      clayton.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "base.mqh"
//+------------------------------------------------------------------+
//| Clayton copula.                                                  |
//+------------------------------------------------------------------+
class CClayton:public CBivariateCopula
  {
private:
   virtual double    theta_hat(const double tau) override
     {
      return 2.0*tau/(1.0-tau);
     }
   vector            generate_pair(double v1, double v2)
     {
      vector out(2);
      double w = 0.0;

      out[0] = v1;
      out[1] = pow(pow(v1,-m_theta)*(pow(v2,(-m_theta / (1 + m_theta))) - 1.0)+1.0,-1.0/m_theta);
      return out;
     }
protected:
   virtual double    pdf(double u,double v) override
     {
      double u_part,v_part,pd;
      u_part = pow(u,(-1 - m_theta));
      v_part = pow(v,(-1 - m_theta));
      pd = ((1.0 + m_theta) * u_part * v_part*pow(-1.0 + u_part * u + v_part * v, (-2.0 - 1.0 / m_theta)));
      return pd;
     }
   virtual double    cdf(double u, double v) override
     {
      double expo = pow(MathMax(pow(u,-m_theta)+pow(v,-m_theta)-1.0,0.0),-1.0/m_theta);

      return expo;
     }
   virtual double    condi_cdf(double u, double v) override
     {
      double unt = pow(u,-m_theta);
      double vnt = pow(v,-m_theta);
      double tpow = 1.0/m_theta + 1.0;
      double denominator = pow(unt+vnt-1.0,tpow);
      if(denominator)
         return vnt/v/pow(unt+vnt-1.0,tpow);
      else
         return EMPTY_VALUE;
     }
   virtual double    inv_condi_cdf(double y, double V) override
     {
      if(m_theta < 0.0)
         return V;
      else
        {
         double a = pow(y,m_theta/(-1.0 - m_theta));
         double b = pow(V, m_theta);

         if(b==0)
            return 1.0;
         return pow((a + b - 1.0)/b, -1.0/m_theta);
        }
     }
   virtual vector    inv_condi_cdf(vector& y, vector& V) override
     {
      if(m_theta < 0.0)
         return V;
      else
        {
         vector a = pow(y,m_theta/(-1.0 - m_theta));
         vector b = pow(V, m_theta);

         if(b.CompareEqual(vector::Zeros(b.Size()))==0)
            return vector::Ones(V.Size());
         return pow((a + b - 1.0)/b, -1.0/m_theta);
        }
     }
   virtual vector    pdf(vector &u,vector &v) override
     {
      vector u_part,v_part,pd;
      u_part = pow(u,(-1 - m_theta));
      v_part = pow(v,(-1 - m_theta));
      pd = ((1.0 + m_theta) * u_part * v_part*pow(-1.0 + u_part * u + v_part * v, (-2.0 - 1.0 / m_theta)));
      return pd;
     }
   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 unt = pow(u,-m_theta);
      vector vnt = pow(v,-m_theta);
      double tpow = 1.0/m_theta + 1.0;
      return vnt/v/pow(unt+vnt-1.0,tpow);
     }
public:
                     CClayton(void)
     {
      m_threshold = 1.e-10;
      m_bounds[0] = -1.0;
      m_bounds[1] = DBL_MAX;
      m_invalid_theta = 0.0;
      m_copula_type = CLAYTON_COPULA;
     }
                    ~CClayton(void)
     {
     }
   virtual matrix    Sample(ulong num_samples) override
     {
      double unf_v[],unf_c[];

      vector v,c,u;

      if(!MathRandomUniform(0.0,1.0,int(num_samples),unf_v) ||
         !MathRandomUniform(0.0,1.0,int(num_samples),unf_c) ||
         !v.Assign(unf_v) || !c.Assign(unf_c))
        {
         Print(__FUNCTION__, " failed to get uniform random numbers ", GetLastError());
         return matrix::Zeros(0,0);
        }

      matrix out(num_samples,2);

      for(ulong irow = 0; irow<num_samples; irow++)
         out.Row(generate_pair(v[irow],c[irow]),irow);

      return out;

     }

  };
//+------------------------------------------------------------------+

Hier sehen Sie ein Streudiagramm eines Datensatzes, der aus einer synthetischen bivariaten Clayton-Verteilung entnommen wurde. 

Bivariater Clayton-Stichproben-Datensatz


Bivariate Gumbel-Copula

Die bivariate Gumbel-Copula ist eine archimedische Copula, die effektiv die Abhängigkeit von oberen Schwänzen modelliert und dabei asymmetrisch zwischen den oberen und unteren Schwänzen bleibt. Sie wird durch die Generatorfunktion definiert:

Gumbel-Generator-Funktion

Größere Werte von theta weisen auf eine stärkere Abhängigkeit hin, größere Werte von θ auf eine stärkere Abhängigkeit. Die Gumbel-Copula weist eine Abhängigkeit des oberen Randes mit Koeffizient auf:

Gumbel-Rand-Abhängigkeit

Und keine Abhängigkeit des unteren Rands, was sie besonders nützlich macht, um die Tendenz von extrem hohen Werten in einer Variablen mit extrem hohen Werten in einer anderen Variable zu erfassen. Sie kann nur positive Abhängigkeiten abbilden und konvergiert zur Unabhängigkeits-Copula, wenn θ gleich eins ist. Die Gumbel-Copula ist als CGumbel-Klasse implementiert, die in gumbel.mqh definiert ist.

//+------------------------------------------------------------------+
//|                                                       gumbel.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "base.mqh"
//+------------------------------------------------------------------+
//| Gumbel Copula.                                                   |
//+------------------------------------------------------------------+
class CGumbel:public CBivariateCopula
  {
private:
   class CBrent1:public CBrentQ
     {
   public:
                     CBrent1(void)
        {
        }
                    ~CBrent1(void)
        {
        }
      virtual double objective(double u, double v,double y)
        {
         return (u*(1.0 - log(u)/v)) - y;
        }
     };
   virtual double    theta_hat(const double tau) override
     {
      return 1.0/(1.0-tau);
     }
   vector            generate_pair(double v1, double v2)
     {
      vector out(2);
      double w = 0.0;
      if(v2>m_threshold)
        {
         CBrent1 fun;
         w = fun.minimize(m_theta,v2,m_threshold,1.0,2.e-12,4.0*2.e-16,100);
        }
      else
         w = 1.e10;
      out[0] = exp(pow(v1,1.0/m_theta)*log(w));
      out[1] = exp(pow((1.0-v1),1.0/m_theta)*log(w));
      return out;
     }
protected:
   virtual double    pdf(double u,double v) override
     {
      double u_part,v_part,expo,pd;
      u_part = pow(-1.0*log(u),m_theta);
      v_part = pow(-1.0*log(v),m_theta);
      expo = pow(u_part+v_part,1.0/m_theta);
      pd = 1.0/(u*v) * (exp(-1.0*expo)* u_part/(-1.0*log(u)) * v_part/(-1.0*log(v))*(m_theta+expo-1.0)*pow(u_part+v_part,(1.0/m_theta-2.0)));
      return pd;
     }
   virtual double    cdf(double u, double v) override
     {
      double expo = pow(pow(-1.0*log(u),m_theta) + pow(-1.0*log(v),m_theta),(1. / m_theta));

      return exp(-1.0*expo);
     }
   virtual double    condi_cdf(double u, double v) override
     {
      double expo = pow(pow(-1.0*log(u),m_theta)+pow(-1.0*log(v),m_theta),((1 - m_theta) / m_theta));
      return (cdf(u,v) * expo * pow(-1.0*log(v),m_theta-1.0)/v);
     }
   virtual vector    pdf(vector &u,vector &v) override
     {
      vector u_part,v_part,expo,pd;
      u_part = pow(-1.0*log(u),m_theta);
      v_part = pow(-1.0*log(v),m_theta);
      expo = pow(u_part+v_part,1.0/m_theta);
      pd = 1.0/(u*v) * (exp(-1.0*expo)* u_part/(-1.0*log(u)) * v_part/(-1.0*log(v))*(m_theta+expo-1.0)*pow(u_part+v_part,(1.0/m_theta-2.0)));
      return pd;
     }
   virtual vector    cdf(vector &u, vector &v) override
     {

      vector expo = pow(pow(-1.0*log(u),m_theta) + pow(-1.0*log(v),m_theta),(1. / m_theta));
      return exp(-1.0*expo);
     }
   virtual vector    condi_cdf(vector &u, vector &v) override
     {

      vector expo = pow(pow(-1.0*log(u),m_theta)+pow(-1.0*log(v),m_theta),((1 - m_theta) / m_theta));
      return (cdf(u,v) * expo * pow(-1.0*log(v),m_theta-1.0)/v);
     }
public:
                     CGumbel(void)
     {
      m_threshold = 1.e-10;
      m_bounds[0] = 1.0;
      m_bounds[1] = DBL_MAX;
      m_copula_type = GUMBEL_COPULA;
     }
                    ~CGumbel(void)
     {
     }
   virtual matrix    Sample(ulong num_samples) override
     {
      double unf_v[],unf_c[];

      vector v,c,u;

      if(!MathRandomUniform(0.0,1.0,int(num_samples),unf_v) ||
         !MathRandomUniform(0.0,1.0,int(num_samples),unf_c) ||
         !v.Assign(unf_v) || !c.Assign(unf_c))
        {
         Print(__FUNCTION__, " failed to get uniform random numbers ", GetLastError());
         return matrix::Zeros(0,0);
        }

      matrix out(num_samples,2);

      for(ulong irow = 0; irow<num_samples; irow++)
         out.Row(generate_pair(v[irow],c[irow]),irow);

      return out;

     }

  };
//+------------------------------------------------------------------+

Das folgende Streudiagramm zeigt Daten, die aus einer Gumbel-Copula mit dem Theta-Parameter 5 abgetastet wurden.

Bivariater Gumbel-Stichproben-Datensatz


Bivariate Joe-Copula

Die bivariate Joe-Copula ist eine archimedische Copula, die dafür bekannt ist, dass sie eine starke Abhängigkeit am oberen Ende modelliert und gleichzeitig eine Asymmetrie zwischen dem oberen und unteren Ende aufweist. Sie erfasst Situationen, in denen extrem hohe Werte in einer Variablen wahrscheinlich mit extrem hohen Werten in der anderen Variable zusammenfallen, aber nicht notwendigerweise für niedrige Werte. Die Copula wird durch die Generatorfunktion definiert:

Joe-Generator-Funktion

Größere Werte von θ bedeuten eine stärkere Assoziation. Die Joe-Copula weist immer eine positive Abhängigkeit auf und gehört zur Familie der nicht-elliptischen Copulas, was sie flexibel für die Modellierung nicht-linearer Beziehungen macht. Wichtig ist, dass er einen Abhängigkeitskoeffizienten am oberen Rand aufweist:

Joe-Rand-Abhängigkeit

Und keine Abhängigkeit von der unteren Spitze. Der Code zur Implementierung der joe-Copula ist in joe.mqh aufgeführt.

//+------------------------------------------------------------------+
//|                                                          joe.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "base.mqh"
//+------------------------------------------------------------------+
//| Joe Copula.                                                      |
//+------------------------------------------------------------------+
class CJoe:public CBivariateCopula
  {
private:
   class CInt_Function_1_Func : public CIntegrator1_Func
     {
   private:
      double         m_th;
   public:
      //---
                     CInt_Function_1_Func(void) {}
                    ~CInt_Function_1_Func(void) {}
      void              set_theta(double theta)
        {
         m_th = theta;
        }
      virtual void      Int_Func(double x,double xminusa,double bminusx,double &y,CObject &obj)
        {
         y = (1.0 - pow(1.0 - x,m_th)) * pow(1.0 - x,1.0 - m_th) * log(1.0 - pow(1.0 - x,m_th)) / m_th;
        }
     };
   class CBrent1:public CBrentQ
     {
   public:
                     CBrent1(void)
        {
        }
                    ~CBrent1(void)
        {
        }
      virtual double objective(double u, double v,double y)
        {
         return (u-1.0/v*(log(1.0-pow(1.0-u,v))*(1.0-pow(1.0-u,v))/pow(1.0-u,(v-1.0)))) - y;
        }
     };
   class CBrent2:public CBrentQ
     {
   public:
                     CBrent2(void)
        {
        }
                    ~CBrent2(void)
        {
        }
      virtual double objective(double u, double v,double y)
        {
         CInt_Function_1_Func fint;
         fint.set_theta(u);
         CObject obj;
         CAutoGKStateShell s;
         double integral;
         CAutoGKReportShell rep;
         //---
         CAlglib::AutoGKSmooth(0.0,1.0,s);
         //---
         CAlglib::AutoGKIntegrate(s,fint,obj);
         //---
         CAlglib::AutoGKResults(s,integral,rep);

         CAutoGKReport report = rep.GetInnerObj();

         if(report.m_terminationtype<0.0)
            Print(__FUNCTION__, " integration error ",report.m_terminationtype);

         return (1.0+4.0*integral)-y;
        }
     };
   virtual double    theta_hat(const double tau) override
     {
      CBrent2 fun;
      return fun.minimize(0,tau,1.0,100.0,2.e-12,4.0*2.e-16,100);
     }
   vector            generate_pair(double v1, double v2)
     {
      vector out(2);
      double w = 0;
      if(v2>m_threshold)
        {
         CBrent1 fun;
         w = fun.minimize(m_theta,v2,m_threshold,1.0-m_threshold,2.e-12,4.0*2.e-16,100);
        }
      else
         w = m_threshold;
      out[0] = 1.0 - pow(1.0 - pow(1.0 - pow(1.0 - w, m_theta), v1),(1.0 / m_theta));
      out[1] = 1.0 - pow(1.0 - pow(1.0 - pow(1.0 - w, m_theta), (1.0-v1)),(1.0 / m_theta));
      return out;
     }
protected:
   virtual double    pdf(double u,double v) override
     {
      double up,vp,pd;
      up = pow(1.0-u,m_theta);
      vp = pow(1.0-v,m_theta);
      pd = (up/(1.0-u)*vp/(1.0-v)*pow(up+vp-up*vp,1.0/m_theta-2.0)*(m_theta-(up-1.0)*(vp-1.0)));
      return pd;
     }
   virtual double    cdf(double u, double v) override
     {
      double up = pow(1.0-u,m_theta);
      double vp = pow(1.0-v,m_theta);
      return 1.0 - pow(up+vp-up*vp,1.0/m_theta);
     }
   virtual double    condi_cdf(double u, double v) override
     {
      double up = pow(1.0-u,m_theta);
      double vp = pow(1.0-v,m_theta);
      return (-(-1.0+up)*pow(up+vp-up*vp,-1.0+1.0/m_theta)*vp/(1.0-v));
     }
   virtual vector    pdf(vector &u,vector &v) override
     {
      vector up,vp,pd;
      up = pow(1.0-u,m_theta);
      vp = pow(1.0-v,m_theta);
      pd = (up/(1.0-u)*vp/(1.0-v)*pow(up+vp-up*vp,1.0/m_theta-2.0)*(m_theta-(up-1.0)*(vp-1.0)));
      return pd;
     }
   virtual vector    cdf(vector &u, vector &v) override
     {
      vector up = pow(1.0-u,m_theta);
      vector vp = pow(1.0-v,m_theta);
      return 1.0 - pow(up+vp-up*vp,1.0/m_theta);
     }
   virtual vector    condi_cdf(vector &u, vector &v) override
     {

      vector up = pow(1.0-u,m_theta);
      vector vp = pow(1.0-v,m_theta);
      return (-1.0*(-1.0+up)*pow(up+vp-up*vp,-1.0+1.0/m_theta)*vp/(1.0-v));
     }
public:
                     CJoe(void)
     {
      m_threshold = 1.e-10;
      m_copula_type = JOE_COPULA;
      m_bounds[0] = 1.0;
      m_bounds[1] = DBL_MAX;
     }
                    ~CJoe(void)
     {
     }
   virtual matrix    Sample(ulong num_samples) override
     {
      double unf_v[],unf_c[];

      vector v,c,u;

      if(!MathRandomUniform(0.0,1.0,int(num_samples),unf_v) ||
         !MathRandomUniform(0.0,1.0,int(num_samples),unf_c) ||
         !v.Assign(unf_v) || !c.Assign(unf_c))
        {
         Print(__FUNCTION__, " failed to get uniform random numbers ", GetLastError());
         return matrix::Zeros(0,0);
        }

      matrix out(num_samples,2);

      for(ulong irow = 0; irow<num_samples; irow++)
         out.Row(generate_pair(v[irow],c[irow]),irow);

      return out;

     }

  };
//+------------------------------------------------------------------+

Hier ist die Abhängigkeit anhand eines synthetischen Datensatzes zu sehen, der aus einer Joe-Copula mit dem Theta-Parameter 5 gezogen wurde.

Bivariater Joe-Beispieldatensatz


Bivariate N13-Copula

Die N13-Copula ist eine archimedische Ein-Parameter-Familie, die nur positive Abhängigkeit modelliert und im Gegensatz zu einigen anderen archimedischen Familien keine asymptotische Randabhängigkeit aufweist. Mit steigendem Familienparameter θ nimmt die Gesamtübereinstimmung zu, und an der unteren Parametergrenze (θ=0) nähert sie sich der Unabhängigkeit. Da es keine Randkoeffizienten ungleich Null gibt, ist die N13 nützlich, wenn eine größere Abhängigkeit im mittleren Bereich oder in der oberen Mitte als bei den Gauß- oder Frank-Familien benötigt wird, im Gegensatz zum Clustering von gleichzeitigen Extremen. Die N13-Copula wird durch die Generatorfunktion definiert:

n13-Generator-Funktion

Sie ist in MQL5 als die Klasse CN13 implementiert, die in n13.mqh definiert ist. 

//+------------------------------------------------------------------+
//|                                                          n13.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "base.mqh"
//+------------------------------------------------------------------+
//| N13 Copula (Nelsen 13)                                           |
//+------------------------------------------------------------------+
class CN13:public CBivariateCopula
  {
private:
   class CInt_Function_1_Func : public CIntegrator1_Func
     {
   private:
      double         m_th;
   public:
      //---
                     CInt_Function_1_Func(void) {}
                    ~CInt_Function_1_Func(void) {}
      void              set_theta(double theta)
        {
         m_th = theta;
        }
      virtual void      Int_Func(double x,double xminusa,double bminusx,double &y,CObject &obj)
        {
         y = -((x - x * pow((1 - log(x)),1 - m_th) - x * log(x)) / m_th);
        }
     };
   class CBrent1:public CBrentQ
     {
   public:
                     CBrent1(void)
        {
        }
                    ~CBrent1(void)
        {
        }
      virtual double objective(double u, double v,double y)
        {
         return (u+1.0/v*(u-u*pow(1.0-1.0*log(u),1.0-v)-u*log(u))) - y;;
        }
     };
   class CBrent2:public CBrentQ
     {
   public:
                     CBrent2(void)
        {
        }
                    ~CBrent2(void)
        {
        }
      virtual double objective(double u, double v,double y)
        {
         CInt_Function_1_Func fint;
         fint.set_theta(u);
         CObject obj;
         CAutoGKStateShell s;
         double integral;
         CAutoGKReportShell rep;
         //---
         CAlglib::AutoGKSmooth(0.0,1.0,s);
         //---
         CAlglib::AutoGKIntegrate(s,fint,obj);
         //---
         CAlglib::AutoGKResults(s,integral,rep);

         CAutoGKReport report = rep.GetInnerObj();

         if(report.m_terminationtype<0.0)
            Print(__FUNCTION__, " integration error ",report.m_terminationtype);

         return (1.0 + 4.0*integral) - y;
        }
     };
   virtual double    theta_hat(const double tau) override
     {
      CBrent2 fun;
      return fun.minimize(0,tau,1.e-7,100.0,2.e-12,4.0*2.e-16,100);
     }
   vector            generate_pair(double v1, double v2)
     {
      vector out(2);
      double w = 0.0;
      if(v2>m_threshold)
        {
         CBrent1 fun;
         w = fun.minimize(m_theta,v2,m_threshold,1.0-m_threshold,2.e-12,4.0*2.e-16,100);
        }
      else
         w = m_threshold;
      out[0] = exp(1. - pow(v1 * (pow((1.0 - log(w)),m_theta) - 1.) + 1.,(1. / m_theta)));
      out[1] = exp(1. - pow((1.0-v1) * (pow((1.0 - log(w)),m_theta) - 1.) + 1.,(1. / m_theta)));
      return out;
     }
protected:
   virtual double    pdf(double u,double v) override
     {
      double Cuv,u_part,v_part,numerator,denominator;
      Cuv = cdf(u,v);
      u_part = pow(1.0 - log(u),m_theta);
      v_part = pow(1.0 - log(v),m_theta);
      numerator = Cuv * u_part *v_part * (-1.0+m_theta + pow(-1.0+u_part +v_part,1.0/m_theta))*pow(-1.0+u_part+v_part,1.0/m_theta);
      denominator = u*v*(1.0-1.0*log(u))*(1.0 - log(v))*pow(-1.0+u_part+v_part,2.0);
      return (numerator+1.e-8)/(denominator+1.e-8);
     }
   virtual double    cdf(double u, double v) override
     {
      double u_part = pow(1.0-1.0*log(u),m_theta);
      double v_part = pow(1.0-1.0*log(v),m_theta);
      double cdf = exp(1.0 - pow(-1.0+u_part+v_part,1.0/m_theta));
      return cdf;
     }
   virtual double    condi_cdf(double u, double v) override
     {
      if(!m_theta)
         return EMPTY_VALUE;

      double u_part = pow(1.0 - log(u),m_theta);
      double v_part = pow(1.0 - log(v),m_theta);
      double cuv = cdf(u,v);

      double numerator = cuv *pow(-1.0+u_part+v_part,1.0/m_theta)*v_part;
      double denominator = v*(-1.0+u_part+v_part)*(1.0-1.0*log(v));

      return (numerator+1.e-8)/(denominator+1.e-8);
     }
   virtual vector    pdf(vector &u,vector &v) override
     {
      vector Cuv,u_part,v_part,numerator,denominator;
      Cuv = cdf(u,v);
      u_part = pow(1.0 - log(u),m_theta);
      v_part = pow(1.0 - log(v),m_theta);
      numerator = Cuv * u_part *v_part * (-1.0+m_theta + pow(-1.0+u_part +v_part,1.0/m_theta))*pow(-1.0+u_part+v_part,1.0/m_theta);
      denominator = u*v*(1.0-1.0*log(u))*(1.0 - log(v))*pow(-1.0+u_part+v_part,2.0);
      return (numerator+1.e-8)/(denominator+1.e-8);
     }
   virtual vector    cdf(vector &u, vector &v) override
     {
      vector u_part = pow(1.0-1.0*log(u),m_theta);
      vector v_part = pow(1.0-1.0*log(v),m_theta);
      vector cdf = exp(1.0 - pow(-1.0+u_part+v_part,1.0/m_theta));
      return cdf;
     }
   virtual vector    condi_cdf(vector &u, vector &v) override
     {

      vector u_part = pow(1.0 - log(u),m_theta);
      vector v_part = pow(1.0 - log(v),m_theta);
      vector cuv = cdf(u,v);

      vector numerator = cuv *pow(-1.0+u_part+v_part,1.0/m_theta)*v_part;
      vector denominator = v*(-1.0+u_part+v_part)*(1.0-1.0*log(v));

      return (numerator+1.e-8)/(denominator+1.e-8);
     }
public:
                     CN13(void)
     {
      m_threshold = 1.e-10;
      m_bounds[0] = 0.0;
      m_bounds[1] = DBL_MAX;
      m_copula_type = N13_COPULA;
     }
                    ~CN13(void)
     {
     }
   virtual matrix    Sample(ulong num_samples) override
     {
      double unf_v[],unf_c[];

      vector v,c,u;

      if(!MathRandomUniform(0.0,1.0,int(num_samples),unf_v) ||
         !MathRandomUniform(0.0,1.0,int(num_samples),unf_c) ||
         !v.Assign(unf_v) || !c.Assign(unf_c))
        {
         Print(__FUNCTION__, " failed to get uniform random numbers ", GetLastError());
         return matrix::Zeros(0,0);
        }

      matrix out(num_samples,2);

      for(ulong irow = 0; irow<num_samples; irow++)
         out.Row(generate_pair(v[irow],c[irow]),irow);

      return out;

     }

  };
//+------------------------------------------------------------------+

Es folgt eine Visualisierung eines Datensatzes, der mit einer N13-Copula abgetastet wurde.

Bivariater N13-Stichproben-Datensatz


Bivariate N14-Copula

Die N14-Copula ist eine weitere archimedische Ein-Parameter-Familie, die ebenfalls nur die positive Abhängigkeit modelliert, aber die Tail-Abhängigkeit in beiden Ecken zulässt. Sie führt zu einer Abhängigkeit am oberen Ende und typischerweise zu einer schwächeren Abhängigkeit am unteren Ende, wenn sich der Parameter θ von der Unabhängigkeit entfernt. Die N14-Copula kann die Tendenz gemeinsamer extrem hoher Werte und in geringerem Maße auch gemeinsamer extrem niedriger Werte erfassen. Die Abhängigkeit nimmt mit dem Parameter zu, und die Copula tendiert an der unteren Grenze zur Unabhängigkeit. Empirische Studien über Finanzpaare zeigen häufig, dass N14 aufgrund dieser asymmetrischen Randmerkmale gut zu den realen Daten passt. Die N14-Copula hat die folgende Generatorfunktion:

n14 Generator Funktion

Die Klasse CN14, definiert in n14.mqh, ist die MQL5-Darstellung der N14-Copula. 

//+------------------------------------------------------------------+
//|                                                          n14.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "base.mqh"
//+------------------------------------------------------------------+
//| N14 Copula (Nelsen 14)                                           |
//+------------------------------------------------------------------+
class CN14:public CBivariateCopula
  {
private:
   class CBrent:public CBrentQ
     {
   public:
                     CBrent(void)
        {
        }
                    ~CBrent(void)
        {
        }
      virtual double objective(double u, double v,double y) override
        {
         return ((-1.0*u) * (-2.0 + pow(u,1.0/v))) - y;
        }
     };
   virtual double    theta_hat(const double tau) override
     {
      return (1.0 + tau)/(2.0 - 2.0*tau);
     }
   vector            generate_pair(double v1, double v2)
     {
      vector out(2);
      double w = 0.0;
      if(v2>m_threshold)
        {
         CBrent brent;
         w = brent.minimize(m_theta,v2,m_threshold,1.0-m_threshold,2.e-12,4.0*2.e-16,100);
        }
      else
         w = m_threshold;
      out[0] = pow(1.0 + pow(v1 * pow(pow(w,-1.0/m_theta)-1.0,m_theta),1.0/m_theta),-1.0*m_theta);
      out[1] = pow(1.0 + pow((1.0-v1) * pow(pow(w,-1.0/m_theta)-1.0,m_theta),1.0/m_theta),-1.0*m_theta);
      return out;
     }
protected:
   virtual double    pdf(double u,double v) override
     {
      double u_ker,v_ker,u_part,v_part,cdf_ker,numerator,denominator;
      u_ker = -1.0+pow(u,1.0/m_theta);
      v_ker = -1.0+pow(v,1.0/m_theta);
      u_part = pow(-1.0 + pow(u,-1.0/m_theta),m_theta);
      v_part = pow(-1.0 + pow(v,-1.0/m_theta),m_theta);
      cdf_ker = 1.0 + pow(u_part+v_part,1.0/m_theta);
      numerator = (u_part * v_part *(cdf_ker - 1.0)*(-1.0 + m_theta + 2.0*m_theta*(cdf_ker-1.0)));
      denominator = pow(u_part + v_part,2.0) * pow(cdf_ker,(2.0 + m_theta))* u * v * u_ker * v_ker * m_theta;

      return (numerator+1.e-8)/(denominator+1.e-8);
     }
   virtual double    cdf(double u, double v) override
     {
      double u_part = pow(-1.0+pow(u,-1.0/m_theta),m_theta);
      double v_part = pow(-1.0+pow(v,-1.0/m_theta),m_theta);
      double cdf = pow(1.0 + pow(u_part+v_part,1.0/m_theta),-1*m_theta);
      return cdf;
     }
   virtual double    condi_cdf(double u, double v) override
     {
      double v_ker = -1.0 + pow(v, -1.0 / m_theta);
      double u_part = pow(-1.0 + pow(u, -1.0 / m_theta),m_theta);
      double v_part = pow(-1.0 + pow(v, -1.0 / m_theta),m_theta);
      double cdf_ker = 1.0 + pow(u_part + v_part, (1.0/ m_theta));

      double numerator = v_part * (cdf_ker - 1.0);
      double denominator = pow(v, (1.0 + 1.0 / m_theta)) * v_ker * (u_part + v_part) * pow(cdf_ker,1.0 + m_theta);

      return (numerator+1.e-8)/(denominator+1.e-8);
     }
   virtual vector    pdf(vector&u,vector&v) override
     {
      vector u_ker,v_ker,u_part,v_part,cdf_ker,numerator,denominator;
      u_ker = -1.0+pow(u,1.0/m_theta);
      v_ker = -1.0+pow(v,1.0/m_theta);
      u_part = pow(-1.0 + pow(u,-1.0/m_theta),m_theta);
      v_part = pow(-1.0 + pow(v,-1.0/m_theta),m_theta);
      cdf_ker = 1.0 + pow(u_part+v_part,1.0/m_theta);
      numerator = (u_part * v_part *(cdf_ker - 1.0)*(-1.0 + m_theta + 2.0*m_theta*(cdf_ker-1.0)));
      denominator = pow(u_part + v_part,2.0) * pow(cdf_ker,(2.0 + m_theta))* u * v * u_ker * v_ker * m_theta;

      return (numerator+1.e-8)/(denominator+1.e-8);
     }
   virtual vector    cdf(vector&u, vector&v) override
     {
      vector u_part = pow(-1.0+pow(u,-1.0/m_theta),m_theta);
      vector v_part = pow(-1.0+pow(v,-1.0/m_theta),m_theta);
      vector cdf = pow(1.0 + pow(u_part+v_part,1.0/m_theta),-1*m_theta);
      return cdf;
     }
   virtual vector    condi_cdf(vector&u, vector&v) override
     {
      vector v_ker = -1.0 + pow(v, -1.0 / m_theta);
      vector u_part = pow(-1.0 + pow(u, -1.0 / m_theta),m_theta);
      vector v_part = pow(-1.0 + pow(v, -1.0 / m_theta),m_theta);
      vector cdf_ker = 1.0 + pow(u_part + v_part, (1.0/ m_theta));

      vector numerator = v_part * (cdf_ker - 1.0);
      vector denominator = pow(v, (1.0 + 1.0 / m_theta)) * v_ker * (u_part + v_part) * pow(cdf_ker,1.0 + m_theta);

      return (numerator+1.e-8)/(denominator+1.e-8);
     }
public:
                     CN14(void)
     {
      m_threshold = 1.e-10;
      m_bounds[0] = 1.0;
      m_bounds[1] = DBL_MAX;
      m_copula_type = N14_COPULA;
     }
                    ~CN14(void)
     {
     }
   virtual matrix    Sample(ulong num_samples) override
     {
      double unf_v[],unf_c[];

      vector v,c,u;

      if(!MathRandomUniform(0.0,1.0,int(num_samples),unf_v) ||
         !MathRandomUniform(0.0,1.0,int(num_samples),unf_c) ||
         !v.Assign(unf_v) || !c.Assign(unf_c))
        {
         Print(__FUNCTION__, " failed to get uniform random numbers ", GetLastError());
         return matrix::Zeros(0,0);
        }

      matrix out(num_samples,2);

      for(ulong irow = 0; irow<num_samples; irow++)
         out.Row(generate_pair(v[irow],c[irow]),irow);

      return out;

     }
  };
//+------------------------------------------------------------------+

Das nachstehende Streudiagramm zeigt einen Datensatz, der mit einer N14-Copula abgetastet wurde.

Bivariater N14-Stichproben-Datensatz

Wenn wir die bisher implementierten Copulae zählen, haben wir jetzt eine ganze Reihe von Copulae, die verschiedene Arten von Abhängigkeiten in bivariaten Datensätzen erfassen können. Im nächsten Abschnitt wird erörtert, was sich aus den durch Copulae erfassten Abhängigkeitsmodellen ableiten lässt. Dies wird uns helfen, besser zu verstehen, wie wir sie anwenden können.


Interpretation der Copula-Operationen

Es wurde festgestellt, dass eine Copula die Abhängigkeit zwischen Variablen erfasst. Die Parameter der Copula beschreiben die Stärke dieser Abhängigkeit, was leicht zu verstehen ist. Da Copulae in der Wahrscheinlichkeitstheorie verwurzelt sind, können wir auf der Grundlage dieser durch das Modell definierten Charakterisierung der Abhängigkeit auch bestimmte Eigenschaften der einzelnen Variablen ableiten. Dies geschieht durch die Auswertung der CDF, PDF und bedingten CDF einer Copula. Solche Schlussfolgerungen werden auf Variablen gezogen, die die Quantile oder Perzentile der Rohvariablen darstellen. Quantile und Perzentile sind Positionsmaße in der Statistik, die dazu dienen, einen sortierten Datensatz in gleich große Teile zu unterteilen. Sie helfen, die Verteilung und den relativen Stellenwert eines bestimmten Datenpunktes zu veranschaulichen.

Bei gegebenen Quantilen, u und v, stellt die Copula CDF C(u,v) die gemeinsame Wahrscheinlichkeit dar: P(U≤u,V≤v). Damit wird die Wahrscheinlichkeit quantifiziert, dass eine Variable unter ihrem u-Quantil und die andere unter ihrem v-Quantil liegt, wobei ihre gegenseitige Abhängigkeit berücksichtigt wird. Die Auswertung einer Copula bei u=0,8, v=0,9 gibt zum Beispiel die Wahrscheinlichkeit an, dass eine Variable in die untersten 80% ihrer Verteilung fällt, während die andere in die untersten 90% fällt. Im Code wird dies über die Methode Copula_CDF() eines Copula-Objekts durchgeführt.

Ergänzt wird die CDF durch die Wahrscheinlichkeitsdichtefunktion einer Copula, c(u,v), die mit der Methode Copula_PDF() ausgewertet wird. Während die CDF die akkumulierte Wahrscheinlichkeitsmasse bis zu einem Punkt angibt, zeigt die PDF an, wie dicht diese Wahrscheinlichkeitsmasse an dem Punkt selbst konzentriert ist. Ein hoher PDF-Wert bedeutet, dass die bestimmte Kombination von Ergebnissen (u,v) sehr wahrscheinlich ist. Genauer gesagt, ist die Copula-PDF durch die folgende Formel.

Copula PDF

Ein hoher PDF-Wert impliziert eine starke lokale Abhängigkeit zwischen den Variablen im Vergleich zum Fall der Unabhängigkeit. Niedrige Werte oder Werte nahe Null bedeuten, dass ein gemeinsames Ergebnis unwahrscheinlich ist.

Die bedingte Wahrscheinlichkeit kann berechnet werden, indem die partielle Ableitung der CDF der Copula nach einer Variablen ermittelt wird, die oft als C(v∣u) oder C(u∣v) bezeichnet wird. Damit wird das bedingte Verhalten des einen Quantils erfasst, wenn das andere auf einem bestimmten Niveau fixiert ist. Im Zusammenhang mit dem Risikomanagement könnte man fragen: „Wenn die Rendite von Anlage A beim 10. Perzentil liegt (u=0,10), wie hoch ist die Wahrscheinlichkeit, dass die Rendite von Anlage B beim oder unter dem 5. Perzentil liegt (V≤0,05)?“. Dies wird durch die Methode Conditional_Probability() realisiert. Die erste Eingabe in die Methode entspricht der Variablen, die fixiert ist, und die zweite gibt das bedingte Quantil an. Schließlich liefert die Methode Inv_Conditional_Probability() die Umkehrung der bedingten CDF. Bei einer bedingten Wahrscheinlichkeit y und einem bedingten Quantil v wird u so bestimmt, dass C(v∣u)=y ist. Im nächsten Abschnitt wird gezeigt, wie die Copula-Inferenz auf die Formulierung von Paarhandelsstrategien angewendet werden kann.


Copula-basierte Paarhandelsstrategien

Traditionelle Paarhandelsmethoden, wie die Distanzmethode und die Kointegration, scheitern manchmal daran, dass sie nicht in der Lage sind, die nichtlineare oder asymmetrische Beziehung zwischen den Preisen von Vermögenswerten zu berücksichtigen, da sie davon ausgehen, dass die Handelsspanne einer Normalverteilung und einer linearen Beziehung folgt. Die Copula-basierte Methode könnte theoretisch eine Lösung für dieses Problem bieten, da sie eine flexible Modellierung ermöglicht, die Nicht-Normalität und Tail-Abhängigkeit erfasst. Dieser Rahmen generiert Handelssignale, indem er die Copula verwendet, um bedingte Wahrscheinlichkeiten zu berechnen, die ein Fehlbewertungsereignis definieren. Ein Vermögenswert gilt als unterbewertet, wenn die bedingte Wahrscheinlichkeit einer niedrigen Rendite angesichts der Rendite seines Paares sehr hoch ist. Umgekehrt ist sie überbewertet, wenn diese Wahrscheinlichkeit sehr gering ist. Die Handelseinträge basieren daher nicht auf linearen Schwellenwerten, sondern auf einem quantilbasierten Konfidenzniveau, das aus der gemeinsamen Verteilung abgeleitet wird:

Copula-Signale

Trotz der vorgeschlagenen hypothetischen Eignung von Copulae für die Modellierung der Abhängigkeit von Vermögenspreisen sollten sich die Leser ihrer Grenzen bewusst sein. Erstens funktioniert eine Copula am besten, wenn sie auf stationäre multivariate Datensätze angewendet wird. Wenn die Daten nicht stationär sind, erfassen die geschätzten Parameter der Randverteilungen und der Copula-Funktion Beziehungen, die nur für den spezifischen Zeitraum gelten, für den sie geschätzt wurden, was zu unzuverlässigen Schlussfolgerungen und schlechten Prognosen für künftige Zeiträume führt, in denen sich die Eigenschaften verschoben haben können. Nicht-stationäre Zeitreihen weisen häufig eine hohe Korrelation oder Abhängigkeit auf, die allein auf gemeinsame Trends zurückzuführen ist. Wenn die Randverteilungen nicht stationär sind, könnte ein Copula-Modell eine Abhängigkeitsstruktur erfassen, die lediglich ein Artefakt dieser sich ändernden Randeigenschaften ist, und nicht eine echte, zeitinvariante Verbindung zwischen den Variablen.

Bei nicht-stationären Zeitreihen führt die Transformation in eine Gleichverteilung (über die Wahrscheinlichkeits-Integral-Transformation) möglicherweise nicht zu Daten, die unabhängig und identisch verteilt (i.i.d.) im einheitlichen Raum sind, was typischerweise die Eingangsvoraussetzung für die Copula-Schätzung ist. Die Stationarität trägt dazu bei, eine klarere Trennung zwischen dem marginalen Verhalten und der Abhängigkeitsstruktur zu gewährleisten, was der Hauptvorteil der Anwendung eines Copula-Ansatzes ist. Wenn die Margen nicht-stationär sind, könnte die geschätzte Copula implizit die nicht-stationäre Dynamik der Margen erfassen und die beiden Komponenten verwechseln. Trotz dieser Einschränkungen gibt es in der wissenschaftlichen Literatur zahlreiche Beispiele für Paarhandelsstrategien, die auf Copula-Modellen basieren.

Es ist wichtig zu beachten, dass die Anforderung der Stationarität für Standard- oder statische Copula-Modelle gilt. Für Datensätze, die von Natur aus nicht stationär sind, gibt es spezielle Methoden, die angewendet werden können. Die Daten können vorverarbeitet werden, um die resultierenden Reihen stationär zu machen, und die Copula wird dann auf diesen gefilterten Datensatz angewendet. Fortgeschrittenere Modelle, wie z. B. zeitvariable oder nicht-stationäre Copulae, erlauben es, dass sich der Abhängigkeitsparameter im Laufe der Zeit ändert, was häufig durch eine Zeitvariable oder andere Kovariaten bedingt ist. Diese Modelle sind ausdrücklich für den Umgang mit nicht-stationären Daten konzipiert. Solche Copulae werden Gegenstand eines anderen Artikels sein. Vorerst untersuchen wir die Wirksamkeit von Standard- oder statischen Copulae bei der Modellierung der Abhängigkeit eines Paares von Rohdatenpreisen. Um diese Art von Strategie effektiv anwenden zu können, müssen wir zunächst eine Methode zur Identifizierung geeigneter Symbole für den Handel festlegen.


Auswahl der zu handelnden Paare

Bei der Auswahl von Symbolen für den Copula-basierten Paarhandel können verschiedene Methoden angewandt werden, einschließlich derer, die in traditionellen Paarhandelsstrategien verwendet werden. Dazu gehören die Kointegrations- und die Distanzmethode. Die Kointegration gilt allgemein als die strengste Methode. Ziel ist es, zwei nicht-stationäre Preisreihen zu finden, deren lineare Kombination stationär ist. Tests wie der Engle-Granger Two-Step oder der Johansen-Test werden auf die Preisreihen angewendet. Ein stationärer Spread impliziert eine stabile, langfristige Gleichgewichtsbeziehung, der die Preise im Allgemeinen folgen werden. Die Abstandsmethode (Summe der quadrierten Abstände - SSD) ist ein einfacheres, nichtparametrisches Verfahren.

Summe der quadrierten Abstände

Bei diesem Ansatz werden Paare ausgewählt, die die historische SSD zwischen ihren normalisierten Preisreihen oder kumulativen Renditen minimieren. Die Paare mit dem niedrigsten SSD werden ausgewählt, da sie sich in der Vergangenheit am engsten zusammen bewegt haben.

In diesem Text werden wir Konkordanzmaße für die Auswahl eines geeigneten Symbolpaares verwenden. Konkordanzmaße sind nicht-parametrische Instrumente zur Quantifizierung der Stärke und Art der Abhängigkeit zwischen den Preisreihen der Vermögenswerte, die das Kernprinzip der Strategie darstellt. Die gängigsten nicht-parametrischen Konkordanzmaße sind:

  • Das Tau von Kendall: Dieses Maß basiert auf dem Unterschied zwischen der Wahrscheinlichkeit, übereinstimmende Paare und nicht übereinstimmende Paare zu beobachten. Es ist ein robustes Maß für die Stärke der Abhängigkeit.
  • Das Rho von Spearman: Dabei handelt es sich im Wesentlichen um den Pearson-Korrelationskoeffizienten, der anhand der gereihten Daten und nicht anhand der Werte selbst berechnet wird. Es misst die Stärke der monotonen Beziehung, d. h. die Tendenz der entsprechenden Variablen, sich in die gleiche oder entgegengesetzte Richtung zu bewegen.

Eine hohe Konkordanz deutet auf eine starke Ko-Bewegung hin. Die Paare mit den höchsten Werten für das tau von Kendall oder das rho von Spearman werden ausgewählt. Ein hoher Wert (nahe 1) deutet auf eine starke Tendenz hin, dass sich die Preise der Symbole gemeinsam bewegen, was auf eine langfristige Beziehung hindeutet, die ein Paarhandel ausnutzen kann. Das MetaTrader 5 Skript, Concordance.ex5, berechnet aus einer Liste von Symbolen das Paar mit der höchsten Konkordanz.

//+------------------------------------------------------------------+
//|                                                  Concordance.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<dependence.mqh>
input string   input_symbols = "AUDUSD,NZDUSD,XAUUSD,XAGUSD,EURUSD,XAUEUR";
input datetime input_start_date = D'2025.01.01 00:00';
input ulong    input_count_bars = 260;
input ENUM_TIMEFRAMES input_time_frame = PERIOD_D1;
input ENUM_DEPENDENCE_MEASURE dependence_measure = KENDAL_TAU;
input bool Use_Returns = false;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string test_symbols[];
   int num_symbols = StringSplit(input_symbols,StringGetCharacter(",",0),test_symbols);
   if(StringFind(input_symbols,",",StringLen(input_symbols)-1) == StringLen(input_symbols)-1)
     {
      --num_symbols;
      ArrayRemove(test_symbols,uint(num_symbols),1);
     }

   if(num_symbols<2)
     {
      Print(" 2 or more symbols expected ");
      return;
     }

   matrix prices = matrix::Zeros(input_count_bars,ulong(num_symbols));
   vector dwnload;
   for(ulong i = 0; i<prices.Cols(); ++i)
     {
      int try = 10;
      ResetLastError();
      while((!dwnload.CopyRates(test_symbols[i],input_time_frame,COPY_RATES_CLOSE,input_start_date,input_count_bars) ||
         !prices.Col(dwnload,i)) && try)
       {
         Sleep(10000);
         --try;
       }
      /*if(StringFind(test_symbols[i],"XAUEUR")>=0)
        {
         prices.Col(dwnload*prices.Col(i-1),i);
        }*/
      if(GetLastError())
       {
        Print("Could not download data");
        return;
       }
     }
     
   if(Use_Returns)
     {
      prices = log(prices);
      prices = np::diff(prices,1,false);
     }

   matrix dep_mat = dependence(prices,dependence_measure);
   Print(dep_mat);
   Print(EnumToString(dependence_measure), "\n","Using ", Use_Returns?"Returns":"Raw prices");
   if(test_symbols.Size()>2)
     {
      matrix mm = dep_mat.TriL(-1);
      int index = int(mm.ArgMax());
      Print("Pair with highest dependence: ", test_symbols[int(index/int(dep_mat.Cols()))], " and ", test_symbols[int(MathMod(index,dep_mat.Cols()))]);
     }
  }
//+------------------------------------------------------------------+

Nachfolgend sehen Sie die Ausgabe eines Laufs mit einem Universum der folgenden Symbole: AUDUSD, NZDUSD, XAUUSD, XAGUSD, EURUSD und XAUEUR.

Konkordanz-Skriptausgabe

Über statistische Messungen hinaus ist es in der Regel wünschenswert, dass die Abhängigkeit eines ausgewählten Paares durch wirtschaftliche Fundamentaldaten untermauert wird. Die Ergebnisse des Testlaufs des Skripts Concordance.ex5 zeigen, dass XAUEUR und XAUUSD das höchste Maß an Ko-Bewegung aufweisen. Die Abhängigkeit dieses Paares lässt sich leicht dadurch erklären, dass sie beide von demselben Basiswert abgeleitet sind. Wir haben also ein geeignetes Symbolpaar für den Handel; der nächste Schritt besteht darin, ein Copula-Modell auszuwählen, das am besten zu den Abhängigkeitsmerkmalen der Symbole passt.


Auswahl eines Copula-Modells

Die wichtigsten Methoden zur Auswahl einer geeigneten Copula sind Modellauswahlkriterien und Goodness-of-Fit-Tests. Modellauswahlkriterien oder Informationskriterien stellen ein Gleichgewicht zwischen der Anpassungsgüte eines Modells und seiner Komplexität (Anzahl der Parameter) her. Ein niedrigerer Wert für diese Kriterien bedeutet im Allgemeinen ein besseres Modell. Die gebräuchlichsten sind das Akaike-Informationskriterium (AIC), das Bayessche Informationskriterium (BIC) und das Hannan-Quinn-Informationskriterium (HQIC).  Diese Methode beruht auf einem dreistufigen Verfahren. Zunächst werden die Parameter mehrerer möglicher Copula-Modelle geschätzt und die Log-Likelihood-Schätzung jedes Modells berechnet. Der nächste Schritt ist die Berechnung der Informationskriterien für jedes Kandidatenmodell, wobei AIC, BIC oder HQIC verwendet werden können. Im letzten Schritt werden die Werte der Informationskriterien aller Kandidatenmodelle verglichen und das Modell mit dem niedrigsten Wert ausgewählt.

Alternativ kann die Auswahl einer geeigneten Copula auch durch Anwendung von Goodness-of-Fit-Tests (GoF) erfolgen. Mit diesen Tests wird statistisch ermittelt, ob die durch ein bestimmtes Copula-Modell erfasste Abhängigkeitsstruktur mit den beobachteten Daten konsistent ist. Es handelt sich um formale Hypothesentests. Wenn die Nullhypothese lautet: Die Daten werden durch die angenommene Copula-Familie erzeugt. Beispiele für Teststatistiken, die verwendet werden können, sind:

  • Cramér-von Mises-Statistik: Misst die quadratische Differenz zwischen der empirischen und der parametrischen Copula.
  • Kolmogorov-Smirnov-Statistik: Misst die maximale absolute Differenz zwischen der empirischen und der parametrischen Copula.

Ein parametrisches Bootstrap-Verfahren wird häufig verwendet, um die Nullverteilung zu simulieren und den p-Wert zu berechnen. Ein hoher p-Wert deutet darauf hin, dass die Nullhypothese nicht zurückgewiesen werden kann, was bedeutet, dass die Copula ein plausibles Modell für die Daten ist. Native MQL5-Implementierungen der Kolmogorov-Smirnov- und Cramér-von Mises-Teststatistiken für Copulae sind nicht verfügbar, sodass wir uns auf die Methode der Modellauswahlkriterien verlassen müssen.

//+------------------------------------------------------------------+
//|                                              CopulaSelection.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\bivariate_copula.mqh>
#include <ECDF\linear_cdf.mqh>
//--- input parameters
input string   FirstSymbol="XAUUSD";
input string   SecondSymbol="XAUEUR";
input datetime TrainingDataStart=D'2025.01.01';
input ulong    HistorySize=260;
input bool     SaveModel = false;
input string   EcdfModel = "model.ecdf";
input string   CopulaModel = "model.copula";
//---
//string   NormalizingSymbol="EURUSD";
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   vector p_1,p_2,p_3,p_n;
   matrix pdata,pobs;
   if(!p_1.CopyRates(FirstSymbol,PERIOD_CURRENT,COPY_RATES_CLOSE,TrainingDataStart,HistorySize) ||
      !p_2.CopyRates(SecondSymbol,PERIOD_CURRENT,COPY_RATES_CLOSE,TrainingDataStart,HistorySize) ||
      p_1.Size()!=p_2.Size() ||
      !pdata.Resize(p_1.Size(),2) ||
      !pdata.Col(p_1,0) ||
      !pdata.Col(p_2,1))
     {
      Print(" failed to collect and initialize rates matrix ", GetLastError());
      return;
     }
//---
   CLinearCDF qt();
   if(!qt.fit(pdata))
      return;
//---
   pobs = qt.to_quantile(pdata);
//---
   if(SaveModel)
     {
      CFileBin file;
      file.Open(EcdfModel,FILE_WRITE|FILE_BIN|FILE_COMMON);
      if(!qt.save(file.Handle()))
         Print(" Failed to save ", EcdfModel);
      file.Close();
     }
//---
   vector lowest = vector::Zeros(8);
   CBivariateCopula *bcop[8];
//---
   bcop[0] = new CClayton();
   bcop[1] = new CFrank();
   bcop[2] = new CGumbel();
   bcop[3] = new CJoe();
   bcop[4] = new CN13();
   bcop[5] = new CN14();
   bcop[6] = new CGaussian();
   bcop[7] = new CStudent();
//---
   for(uint i = 0; i<bcop.Size(); ++i)
     {
      bool fitted = bcop[i].Fit(pobs.Col(0),pobs.Col(1));
      //---
      if(fitted)
        {
         double ll = bcop[i].Log_likelihood(pobs.Col(0),pobs.Col(1));
         lowest[i] = aic(ll,int(pobs.Rows()));
         Print(EnumToString((ENUM_COPULA_TYPE)bcop[i].Type()));
         Print(" sic ", sic(ll,int(pobs.Rows())));
         Print(" aic ", lowest[i]);
         Print(" hqic ", hqic(ll,int(pobs.Rows())));
        }
     }
//---
   if(SaveModel)
     {
      ulong shift = lowest.ArgMin();
      CFileBin file;
      file.Open(CopulaModel,FILE_WRITE|FILE_BIN|FILE_COMMON);
      if(!bcop[shift].Save(file.Handle()))
         Print("Failed to save ", CopulaModel);
      file.Close();
     }
//---
   for(uint i = 0; i<bcop.Size(); delete bcop[i], ++i);
  }
//+------------------------------------------------------------------+

Das Skript CopulaSelection.ex5 ermöglicht es dem Nutzer, ein Symbolpaar, ein Startdatum und die Anzahl der Balken anzugeben, die die Stichproben definieren, die für die Erstellung von Kandidaten-Copula-Modellen verwendet werden sollen. Das Skript berechnet die Log-Likelihood für jedes in Frage kommende Copula-Modell und berechnet dann dessen Informationskriterien. Das Skript ermöglicht es, das ausgewählte Copula-Modell zusammen mit dem entsprechenden empirischen kumulativen Verteilungsfunktionsmodell zu speichern. Die Ergebnisse werden in der Journal-Registerkarte des MetaTrader 5 zur Information des Nutzers ausgegeben. Hier ist die Ausgabe eines Laufs, bei dem das Skript auf die Symbole XAUUSD und XAUEUR angewendet wurde.

CopulaSelection Skriptausgabe

Die Ausgabe des Skripts zeigt deutlich, dass die Frank-Copula das niedrigste Informationskriterium aufweist, was darauf hindeutet, dass sie das am besten passende Modell für den Beispieldatensatz ist. Die gespeicherten Modelle werden in einem Indikator und einem Expert Advisor verwendet, der eine Copula-basierte Paarstrategie implementiert.


Ein Beispiel für eine Copula-basierte Paarhandelsstrategie

Die Strategie, die wir anwenden werden, ist eine modifizierte Version der im Journal of Derivatives & Hedge Funds von Liew, R.Q. und Wu, Y. beschriebenen Strategie, die dem Ansatz folgt, der in Hudson und Thames beschrieben wird. Es stützt sich auf ein Copula-Modell, das auf einer Stichprobe von Preisdaten trainiert wurde, um die Abhängigkeit zwischen den Paaren zu erfassen. Die Idee besteht darin, bedingte Wahrscheinlichkeiten, die aus den Quantilen der aktuellen Preise berechnet werden, zur Generierung von Handelssignalen zu verwenden. Die Eingabelogik ist wie folgt.

  • Wenn C(u_1 | u_2) <= 0,05 und C(u_2 | u_1) >= 0,95 ist, dann gilt Symbol 1 als unterbewertet und Symbol 2 als überbewertet angesehen, was ein gleichzeitiges Kaufsignal für Symbol 1 und ein Verkaufssignal für Symbol 2 erzeugt.
  • Wenn C(u_1 | u_2) >= 0,95 und C(u_2 | u_1) <= 0,05 ist, dann gilt Symbol 2 als unterbewertet und Symbol 1 als überbewertet, was gleichzeitig ein Verkaufssignal für Symbol 1 und ein Kaufsignal für Symbol 2 auslöst.

Die Variablen u_1 und u_2 stellen die Quantile für Symbol 1 bzw. Symbol 2 dar. Die Positionen werden auf eine von zwei Arten geschlossen.

  • Die erste Ausstiegsoption schließt beide Handelsgeschäfte, wenn eine der beiden bedingten Wahrscheinlichkeiten eine obere Ausstiegsschwelle überschreitet oder eine untere Ausstiegsschwelle unterschreitet.
  • Die zweite Ausstiegsoption schließt beide Positionen nur, wenn eine der bedingten Wahrscheinlichkeiten eine obere Ausstiegsschwelle überschreitet und die andere gleichzeitig eine untere Ausstiegsschwelle unterschreitet.

Die Strategie wird aus reiner Neugierde umgesetzt. Theoretisch sollte es offensichtlich sein, dass das Modell schnell zusammenbricht und irrelevant wird, sobald die Preise über die in den Trainingsdaten beobachteten Höchst- oder Tiefstwerte hinaus fallen oder steigen. Das Ziel bei der Umsetzung dieser Strategie ist es, zu beurteilen, ob die Copula in der Lage ist, ausnutzbare Handelsmöglichkeiten zu signalisieren. Um dies effektiv zu tun, wird die Strategie über denselben Zeitraum getestet, der zum Trainieren des Copula-Modells verwendet wurde (In-Sample-Testing). Zu diesem Zweck wurde ein Indikator mit der Bezeichnung GoldCopulaSignals.ex5 erstellt. Der Indikator lädt die vom Skript CopulaSelection.ex5 gespeicherten empirischen CDF- und Copula-Modelle und zeigt die aus den Quantilen der Symbole XAUEUR und XAUUSD berechneten bedingten Wahrscheinlichkeiten an.

//+------------------------------------------------------------------+
//|                                            GoldCopulaSignals.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 indicator_separate_window
#property indicator_buffers 4
#property indicator_plots   4
#property indicator_maximum 1.1
#property indicator_minimum -0.1
#property indicator_level1  0.05
#property indicator_level2  0.5
#property indicator_level3  0.95
#include<Copulas\Bivariate\bivariate_copula.mqh>
#include<ECDF\linear_cdf.mqh>
//--- plot S1
#property indicator_label1  "S1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot S2
#property indicator_label2  "S2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//---
#property indicator_label3  "EURGold"
#property indicator_type3   DRAW_NONE

//---
#property indicator_label4  "USDGold"
#property indicator_type4  DRAW_NONE

//--- input parameters
input ENUM_COPULA_TYPE Copulatype = FRANK_COPULA;
input string   CopulaModelName = "model.copula";
input string   ECDFModelName = "model.ecdf";
//---
string   FirstSymbol="XAUUSD";
string   SecondSymbol="XAUEUR";
//--- indicator buffers
double         S1Buffer[];
double         S2Buffer[];
double         S3Buffer[];
double         S4Buffer[];
matrix         mat_;
CLinearCDF* qt;
CBivariateCopula *bcop;
CFileBin savedcop,savedcdf;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//---
   qt = new CLinearCDF();
//---
   switch(Copulatype)
     {
      case CLAYTON_COPULA:
         bcop = new CClayton();
         break;
      case FRANK_COPULA:
         bcop = new CFrank();
         break;
      case GUMBEL_COPULA:
         bcop = new CGumbel();
         break;
      case JOE_COPULA:
         bcop = new CJoe();
         break;
      case N13_COPULA:
         bcop = new CN13();
         break;
      case N14_COPULA:
         bcop = new CN14();
         break;
      case GAUSSIAN_COPULA:
         bcop = new CGaussian();
         break;
      case STUDENT_COPULA:
         bcop = new CStudent();
         break;
     }
//---
   savedcop.Open(CopulaModelName,FILE_READ|FILE_BIN|FILE_COMMON);
   if(!bcop.Load(savedcop.Handle()))
     {
      Print("Failed to load copula model ", CopulaModelName, " ", GetLastError());
      return INIT_FAILED;
     }
//---
   if((ENUM_COPULA_TYPE)bcop.Type() != Copulatype)
     {
      Print("Invalid copula model. Instantiated copula is ", EnumToString((ENUM_COPULA_TYPE)bcop.Type()),"\n but specified copula in input settings is ", EnumToString(Copulatype));
      return INIT_FAILED;
     }
//---
   savedcop.Close();
//---
   savedcdf.Open(ECDFModelName,FILE_READ|FILE_BIN|FILE_COMMON);
   if(!qt.load(savedcdf.Handle()))
     {
      Print("Failed to load the ECDF model ", ECDFModelName, " ", GetLastError());
      return INIT_FAILED;
     }
//---
   savedcdf.Close();
//---
   mat_.Resize(1,2);
//--- indicator buffers mapping
   SetIndexBuffer(0,S1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,S2Buffer,INDICATOR_DATA);
   SetIndexBuffer(2,S3Buffer,INDICATOR_DATA);
   SetIndexBuffer(3,S4Buffer,INDICATOR_DATA);
//---
   ArraySetAsSeries(S1Buffer,true);
   ArraySetAsSeries(S2Buffer,true);
   ArraySetAsSeries(S3Buffer,true);
   ArraySetAsSeries(S4Buffer,true);
//---
   PlotIndexSetString(0,PLOT_LABEL,FirstSymbol);
   PlotIndexSetString(1,PLOT_LABEL,SecondSymbol);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| On deinitialization                                              |
//+------------------------------------------------------------------+
void  OnDeinit(const int  reason)
  {
//---
   if(CheckPointer(qt) == POINTER_DYNAMIC)
      delete qt;
//---
   if(CheckPointer(bcop) == POINTER_DYNAMIC)
      delete bcop;
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int32_t &spread[])
  {
//---
   int32_t limit;
   if(prev_calculated<=0)
      limit= rates_total-2;
   else
      limit=rates_total-prev_calculated;
//---
   for(int32_t i =limit; i >=0 ; --i)
     {
      mat_[0,0] = iClose(FirstSymbol,PERIOD_CURRENT,i);
      mat_[0,1] = iClose(SecondSymbol,PERIOD_CURRENT,i);
      S3Buffer[i] = mat_[0,1];
      S4Buffer[i] = mat_[0,0];
      mat_ = qt.to_quantile(mat_);
      S1Buffer[i] = bcop.Conditional_Probability(mat_[0][0],mat_[0][1]);
      S2Buffer[i] = bcop.Conditional_Probability(mat_[0][1],mat_[0][0]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Die folgende Abbildung zeigt, wie der Indikator aussieht (Unterfenster).

GoldCopulaSignals Indikator

Die blaue Linie stellt die bedingten Wahrscheinlichkeiten des XAUEUR-Symbols dar, während die rote Linie die Wahrscheinlichkeiten des XAUUSD darstellt. Die Strategie selbst ist als Expert Advisor SimpleCopulaPairsStrategy.ex5 implementiert.

//+------------------------------------------------------------------+
//|                                    SimpleCopulaPairsStrategy.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"
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <TimeOptions.mqh>
#include<Copulas\Bivariate\base.mqh>
#resource "\\Indicators\\GoldCopulaSignals.ex5"
//---
#define USDGOLD "XAUUSD"
#define EURGOLD "XAUEUR"
//--- input parameters
enum ENUM_EXIT_RULE
  {
   AND_EXIT=0,//"And exit"
   OR_EXIT//Or exit
  };
//--- input parameters
input ENUM_COPULA_TYPE Copulatype = FRANK_COPULA;
input string   CopulaModelName = "model.copula";
input string   ECDFModelName = "model.ecdf";
input double   OpenThresholdProbability_First = 0.05;
input double   OpenThresholdProbability_Second = 0.95;
input double   UpperExitThresholdProbability = 0.5;
input double   LowerExitThresholdProbability = 0.5;
input ENUM_EXIT_RULE ExitRule = OR_EXIT;
input ulong    SlippagePoints = 10;
input double   TradingLots = 0.01;
input ulong    MagicNumber = 18973984;
double   StopLossPoints = 0.0;
double   TakeProfitPoints = 0.0;
int  OpenShift = 1;
int  CloseShift = 1;
//---
ENUM_DAY_TIME_MINUTES TradeSessionOpen = 6;
ENUM_DAY_TIME_MINUTES TradeSessionClose = 1435;
//--- indicator buffers
CSymbolInfo usd,eur;
//--
double usd_sigs[2],eur_sigs[2];
int indicator_handle;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!usd.Name(USDGOLD) || !eur.Name(EURGOLD))
     {
      Print("Symbol info config error, ");
      return INIT_FAILED;
     }
//---
   indicator_handle = INVALID_HANDLE;
   int try
         = 10;
   while(indicator_handle == INVALID_HANDLE && try
            >0)
        {
         indicator_handle = iCustom(NULL,PERIOD_CURRENT,"::Indicators\\GoldCopulaSignals.ex5",Copulatype,CopulaModelName,ECDFModelName);
         --try;
        }
//---
   if(indicator_handle == INVALID_HANDLE)
     {
      Print(" failed to initialize the indicator ", GetLastError());
      return INIT_FAILED;
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   datetime sessionopen = iTime(NULL,PERIOD_D1,0) + int(TradeSessionOpen*60);
   datetime sessionclose = iTime(NULL,PERIOD_D1,0) + int(TradeSessionClose*60);
//---
   if(TimeCurrent()<sessionopen || TimeCurrent()>=sessionclose)
      return;
//---
   if(SumMarketOrders(MagicNumber))
     {
      long exit = GetExitSignal(CloseShift);
      if(exit)
        {
         if(CloseAll(MagicNumber,NULL,WRONG_VALUE,SlippagePoints)!=2)
            Print("Failed to close all opened orders ");
        }
     }
   else
     {
      long entry = GetEntrySignal(OpenShift);
      if(entry>0)
        {
         if(!OpenOrder(usd,ORDER_TYPE_BUY,StopLossPoints,TakeProfitPoints,TradingLots,SlippagePoints,MagicNumber))
            Print(" Failed long entry for ", usd.Name());
         if(!OpenOrder(eur,ORDER_TYPE_SELL,StopLossPoints,TakeProfitPoints,TradingLots,SlippagePoints,MagicNumber))
            Print(" Failed short entry for ", eur.Name());
        }
      else
         if(entry<0)
           {
            if(!OpenOrder(eur,ORDER_TYPE_BUY,StopLossPoints,TakeProfitPoints,TradingLots,SlippagePoints,MagicNumber))
               Print(" Failed long entry for ", eur.Name());
            if(!OpenOrder(usd,ORDER_TYPE_SELL,StopLossPoints,TakeProfitPoints,TradingLots,SlippagePoints,MagicNumber))
               Print(" Failed short entry for ", usd.Name());
           }
     }
  }
//+------------------------------------------------------------------+
//| get the entry signal                                             |
//+------------------------------------------------------------------+
long GetEntrySignal(int shift = 1)
  {
   static datetime last;
   if(shift>0)
     {
      if(iTime(NULL,PERIOD_CURRENT,0)<=last)
         return 0;
      else
         last = iTime(NULL,PERIOD_CURRENT,0);
     }
   else
     {
      datetime last = (datetime)MathMax(eur.Time(),usd.Time());
      if(TimeCurrent()<=last)
         return 0;
      //---
      eur.RefreshRates();
      usd.RefreshRates();
     }
//---
   if(CopyBuffer(indicator_handle,0,shift,2,usd_sigs)<2 ||
      CopyBuffer(indicator_handle,1,shift,2,eur_sigs)<2)
     {
      Print(__FUNCTION__, " error copying from indicator buffers ");
      return 0;
     }
   double current_prob_first,current_prob_second;
//---
   current_prob_first = usd_sigs[1];
   current_prob_second = eur_sigs[1];
//---
   if(current_prob_first<=OpenThresholdProbability_First && current_prob_second>=OpenThresholdProbability_Second)
     {
      //Print(__FUNCTION__, " go long ");
      return 1;
     }
   else
      if(current_prob_first>=OpenThresholdProbability_Second && current_prob_second<=OpenThresholdProbability_First)
        {
         //Print(__FUNCTION__, " go short ");
         return -1;
        }
//---
   return 0;
  }
//+------------------------------------------------------------------+
//| get the exit signal                                              |
//+------------------------------------------------------------------+
long GetExitSignal(int shift = 1)
  {
   datetime last = (datetime)MathMax(eur.Time(),usd.Time());

   if(shift>0)
     {
      if(iTime(NULL,PERIOD_CURRENT,0)<=last)
         return 0;
     }
   else
     {
      if(TimeCurrent()<=last)
         return 0;
     }
//---
   eur.RefreshRates();
   usd.RefreshRates();

//---
   if(CopyBuffer(indicator_handle,0,shift,2,usd_sigs)<2 ||
      CopyBuffer(indicator_handle,1,shift,2,eur_sigs)<2)
     {
      Print(__FUNCTION__, " error copying from indicator buffers ");
      return 0;
     }
//---
   double prev_prob_first,prev_prob_second;
   double current_prob_first,current_prob_second;
//---
   prev_prob_first = usd_sigs[0];
   prev_prob_second = eur_sigs[0];
//---
   current_prob_first = usd_sigs[1];
   current_prob_second = eur_sigs[1];
//---
   bool u1_up,u2_dwn,u2_up,u1_dwn;
   u1_up = (prev_prob_first<= LowerExitThresholdProbability && current_prob_first>=UpperExitThresholdProbability);
   u1_dwn = (prev_prob_first>=UpperExitThresholdProbability && current_prob_first<=LowerExitThresholdProbability);
   u2_up = (prev_prob_second<=LowerExitThresholdProbability && current_prob_second>=UpperExitThresholdProbability);
   u2_dwn = (prev_prob_second>=UpperExitThresholdProbability && current_prob_second<=LowerExitThresholdProbability);

   if(ExitRule == AND_EXIT)
     {
      if(u1_up && u2_dwn)
         return -1;
      else
         if(u2_up && u1_dwn)
            return 1;
     }
   else
     {
      if(u1_up || u2_dwn)
         return -1;
      else
         if(u2_up || u1_dwn)
            return 1;
     }
   return 0;
  }
//+------------------------------------------------------------------+
//|Enumerate all market orders                                       |
//+------------------------------------------------------------------+
uint SumMarketOrders(ulong magic_number=ULONG_MAX,const string sym=NULL,const ENUM_POSITION_TYPE ordertype=WRONG_VALUE)
  {
   CPositionInfo posinfo;
//---
   uint count=0;
//---
   for(int i = PositionsTotal()-1; i>-1;--i)
     {
      if(!posinfo.SelectByIndex(i))
         continue;
      if(magic_number<ULONG_MAX && posinfo.Magic()!=long(magic_number))
         continue;
      if(sym!=NULL && posinfo.Symbol()!=sym)
         continue;
      if(ordertype!=WRONG_VALUE && posinfo.PositionType()!=ordertype)
         continue;
      count++;
     }
//---
   return count;
  }
//+------------------------------------------------------------------+
//|Enumerate all market orders                                       |
//+------------------------------------------------------------------+
ENUM_POSITION_TYPE LastOrderType(ulong magic_number=ULONG_MAX)
  {
   CPositionInfo posinfo;
//---
   ENUM_POSITION_TYPE direction_type = WRONG_VALUE;
   string symb = NULL;
//---
   for(int i = PositionsTotal()-1; i>-1;--i)
     {
      if(!posinfo.SelectByIndex(i))
         continue;
      if(magic_number<ULONG_MAX && posinfo.Magic()!=long(magic_number))
         continue;
      symb = posinfo.Symbol();
      direction_type = posinfo.PositionType();
      //---
      switch(direction_type)
        {
         case POSITION_TYPE_BUY:
           {
            if(symb == EURGOLD)
               return POSITION_TYPE_SELL;
            else
               if(symb == USDGOLD)
                  return POSITION_TYPE_BUY;
           }
         case POSITION_TYPE_SELL:
           {
            if(symb == EURGOLD)
               return POSITION_TYPE_BUY;
            else
               if(symb == USDGOLD)
                  return POSITION_TYPE_SELL;
           }
         default:
            return WRONG_VALUE;
        }
     }
//---
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+
//| Profit                                                           |
//+------------------------------------------------------------------+
double Profit(ulong magic_number=ULONG_MAX)
  {
   CPositionInfo posinfo;
//---
   double profit = 0.0;
//---
   for(int i = PositionsTotal()-1; i>-1;--i)
     {
      if(!posinfo.SelectByIndex(i))
         continue;
      if(magic_number<ULONG_MAX && posinfo.Magic()!=long(magic_number))
         continue;
      profit += (posinfo.Profit() + posinfo.Commission() + posinfo.Swap());
     }
//---
   return profit;
  }
//+------------------------------------------------------------------+
//|Open trade                                                        |
//+------------------------------------------------------------------+
bool OpenOrder(CSymbolInfo &syminfo, ENUM_ORDER_TYPE type,double stoplosspoints=0.0, double takeprofitpoints = 0.0,double lotsize=0.1,ulong slippage=10,ulong magic=12345678,string tradecomment=NULL)
  {
   CTrade trade;
//---
   CAccountInfo accinfo;
//---
   trade.SetExpertMagicNumber(magic); // magic
   trade.SetMarginMode();
   trade.SetDeviationInPoints(slippage);
//---
   if(!trade.SetTypeFillingBySymbol(syminfo.Name()))
     {
      Print(__FUNCTION__," Unknown Type filling mode for ", syminfo.Name());
      return false;
     }
//---
   bool ans=false;
   syminfo.RefreshRates();
   string sy = syminfo.Name();
//---
   double price = (type == ORDER_TYPE_BUY)?syminfo.Ask():syminfo.Bid();
//---
   price = NormalizeDouble(MathAbs(price),syminfo.Digits());
//---
   bool result = false;
//---
   switch(type)
     {
      case ORDER_TYPE_BUY:
         if(accinfo.FreeMarginCheck(sy,type,lotsize,price)<0.0)
           {
            Print("Insufficient funds to open long order ("+sy+"). Free Margin = ", accinfo.FreeMargin());
            return false;
           }
         result = trade.Buy(lotsize,sy,price,(stoplosspoints)?NormalizeDouble(price - MathAbs(stoplosspoints*syminfo.Point()),syminfo.Digits()):0.0,(takeprofitpoints)?NormalizeDouble(price + MathAbs(takeprofitpoints*syminfo.Point()),syminfo.Digits()):0.0,tradecomment);
         break;
      case ORDER_TYPE_SELL:
         if(accinfo.FreeMarginCheck(sy,type,lotsize,price)<0.0)
           {
            Print("Insufficient funds to open short order ("+sy+"). Free Margin = ", accinfo.FreeMargin());
            return false;
           }
         result = trade.Sell(lotsize,sy,price,(stoplosspoints)?NormalizeDouble(price + MathAbs(stoplosspoints*syminfo.Point()),syminfo.Digits()):0.0,(takeprofitpoints)?NormalizeDouble(price - MathAbs(takeprofitpoints*syminfo.Point()),syminfo.Digits()):0.0,tradecomment);
         break;
     }
//---
   if(!result)
      Print(__FUNCTION__, " ", trade.CheckResultRetcodeDescription());
//---
   return result;
  }
//+------------------------------------------------------------------+
//| Close market order                                               |
//+------------------------------------------------------------------+
bool CloseOrder(ulong ticket,ulong magic,ulong slp=10)
  {
//---
   CTrade trade;
//---
   trade.SetExpertMagicNumber(magic);
//---
   bool  result = trade.PositionClose(ticket,slp);
//---
   if(!result)
      Print(trade.CheckResultRetcodeDescription());
//---
   return result;
  }
//+------------------------------------------------------------------+
//|  Close all orders                                                |
//+------------------------------------------------------------------+
uint CloseAll(ulong magic_number=ULONG_MAX,const string sym=NULL,const ENUM_POSITION_TYPE ordertype=WRONG_VALUE,const ulong mslip=10)
  {
   CPositionInfo posinfo;
//---
   uint countclosed=0;
//---
   for(int i = PositionsTotal()-1; i>-1;--i)
     {
      if(!posinfo.SelectByIndex(i))
         continue;
      if(magic_number<ULONG_MAX && posinfo.Magic()!=long(magic_number))
         continue;
      if(sym!=NULL && posinfo.Symbol()!=sym)
         continue;
      if(ordertype!=WRONG_VALUE && posinfo.PositionType()!=ordertype)
         continue;
      if(CloseOrder(posinfo.Ticket(),mslip))
         countclosed++;
     }
//---
   return countclosed;
  }
//+------------------------------------------------------------------+

Die Eintritts- und Austrittsschwellen sind vom Nutzer einstellbar, sodass eine Bewertung oder Optimierung dieser Parameter möglich ist. Der EA ermöglicht es den Nutzern auch, ihre eigenen Copula- und empirischen CDF-Modelle zum Testen anzugeben. Es ist wichtig, dass die Leser wissen, dass die hier gezeigten Ergebnisse von denen abweichen können, die bei der Wiederholung dieses Tests aufgrund geringer Unterschiede bei den Symbolraten erzielt werden.

Hier sind die Ergebnisse.

EA-Bericht

Kapitalkurven

Wie man sieht, sind die Ergebnisse recht günstig, was darauf hindeutet, dass diese Art von Strategie von Nutzen sein könnte. Die Leser müssen jedoch bedenken, dass diese Ergebnisse tatsächlich aus einem In-Sample-Test stammen. Wir wissen, dass das Modell zusammenbricht und der Indikator ungültige Signale liefert, wenn die Preise den Trainingsbereich überschreiten. Dies geht aus der nachstehenden Abbildung hervor, die zeigt, wie sich der Indikator verhält, wenn die Kurse in Richtung neuer Höchststände driften.

GoldCopulsSignals-Indikator bricht als Modell zusammen

Nichtsdestotrotz sind die Ergebnisse ermutigend und geben Anstoß für weitere Untersuchungen.


Schlussfolgerung

In diesem Artikel wurde die Implementierung gängiger bivariater, archimedischer Copulae in reinem MQL5 vorgestellt, nämlich die Frank-, Joe-, Gumbel-, Clayton-, N13- und N14-Copulae. Wir haben erörtert, welche Arten von Abhängigkeiten die einzelnen Systeme erfassen können. Später in diesem Artikel haben wir gesehen, wie Copulae auf die Formulierung von Paarhandelsstrategien angewendet werden können. Wir haben sogar ein einfaches Projekt durchgeführt und die Ergebnisse beobachtet. In den nächsten Artikeln werden wir uns näher mit Copulae beschäftigen. Der gesamte Code, auf den in diesem Artikel verwiesen wird, ist unten verfügbar.

Dateien oder Ordner   Beschreibung
MQL5/include/Copulas Dieser Ordner enthält alle Header-Dateien, die alle Copula-Modelle implementieren.
MQL5/include/Brent Dieser Ordner enthält eine Header-Datei, die von den Copula-Implementierungen verwendet wird. 
MQL5/include/ECDF Der Ordner enthält die Header-Dateien für die Implementierung der empirischen kumulativen Verteilungsfunktion
MQL5/include/dependence.mqh Diese Header-Datei enthält Implementierungen von Konkordanzmaßnahmen.
MQL5/include/info_criteria.mqh Diese Datei definiert Funktionen zur Berechnung von AIC, HQIC und BIC. 
MQL5/include/np.mqh Diese Header-Datei definiert verschiedene Vektor- und Matrix-Funktionen. 
MQL5/scripts/Concordance.mq5 Dieses Skript wird verwendet, um aus einer Reihe von Kandidaten ein Symbolpaar mit dem höchsten Maß an Ko-Bewegung zu finden.
MQL5/scripts/CopulaSelection.mq5 Dieses Skript berechnet die Modellauswahlkriterien für alle implementierten Copulae.
MQL5/indicators/GoldCopulaSignals.mq5 Dies ist ein Indikator, der dazu dient, Ein- und Ausstiegssignale für den im Text beschriebenen Expert Advisor zu generieren. 
MQL5/experts/SimpleCopulaPairsStrategy.mq5 Dies ist ein Expert Advisor, der eine Copula-basierte Paarhandelsstrategie implementiert. 

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19931

Beigefügte Dateien |
MetaTrader 5 Machine Learning Blueprint (Teil 5): Sequentielles Bootstrapping – Verzicht auf Kennzeichen, Verbesserung der Ergebnisse MetaTrader 5 Machine Learning Blueprint (Teil 5): Sequentielles Bootstrapping – Verzicht auf Kennzeichen, Verbesserung der Ergebnisse
Sequentielles Bootstrapping gestaltet das Bootstrap-Sampling für maschinelles Lernen im Finanzbereich neu, indem es zeitlich überlappende Kennzeichnungen aktiv vermeidet und so unabhängigere Trainingsstichproben, schärfere Unsicherheitsschätzungen und robustere Handelsmodelle erzeugt. Dieser praktische Leitfaden erklärt die Intuition, zeigt den Algorithmus Schritt für Schritt, bietet optimierte Codemuster für große Datensätze und demonstriert messbare Leistungssteigerungen durch Simulationen und echte Backtests.
Blaupause für maschinelles Lernen (Teil 4): Die versteckte Schwachstelle in Ihrer ML-Pipeline – Gleichzeitigkeit der Kennzeichnungen Blaupause für maschinelles Lernen (Teil 4): Die versteckte Schwachstelle in Ihrer ML-Pipeline – Gleichzeitigkeit der Kennzeichnungen
Entdecken Sie, wie Sie eine kritische Schwachstelle beim maschinellen Lernen im Finanzbereich beheben können, die zu einer Überanpassung der Modelle und einer schlechten Live-Performance führt – die Gleichzeitigkeit der Kennzeichen. Bei der Verwendung der Triple-Barrier-Methode überschneiden sich die Trainingskennzeichen zeitlich, wodurch die zentrale IID-Annahme der meisten ML-Algorithmen verletzt wird. Dieser Artikel bietet eine praktische Lösung in Form einer Stichprobengewichtung. Sie werden lernen, wie man die zeitliche Überlappung zwischen Handelssignalen quantifiziert, Stichprobengewichte berechnet, die die einzigartigen Informationen jeder Beobachtung widerspiegeln, und diese Gewichte in Scikit-Learn implementiert, um robustere Klassifikatoren zu erstellen. Das Erlernen dieser grundlegenden Techniken wird Ihre Handelsmodelle robuster, zuverlässiger und profitabler machen.
Markets Positioning Codex in MQL5 (Teil 1): Bitwise Learning für Nvidia Markets Positioning Codex in MQL5 (Teil 1): Bitwise Learning für Nvidia
Wir beginnen eine neue Artikelserie, die auf unseren früheren Bemühungen aufbaut, die wir in der MQL5-Assistentenserie dargelegt haben, indem wir sie weiterführen und unseren Ansatz zum systematischen Handel und zum Testen von Strategien verstärken. In dieser neuen Serie werden wir uns auf Expert Advisors konzentrieren, die so kodiert sind, dass sie nur eine einzige Art von Position halten - in erster Linie Kaufpositionen. Die Konzentration auf nur einen Markttrend kann die Analyse vereinfachen, die Komplexität der Strategie verringern und einige wichtige Erkenntnisse zutage fördern, vor allem, wenn man nicht nur mit Devisen handelt. In unserer Serie werden wir daher untersuchen, ob dies auch bei Aktien und anderen Nicht-Devisenwerten wirksam ist, wo Nur-Kauf-Systeme in der Regel gut mit Smart-Money- oder institutionellen Strategien korrelieren.
Klassische Strategien neu interpretieren (Teil 18): Suche nach Kerzenmustern Klassische Strategien neu interpretieren (Teil 18): Suche nach Kerzenmustern
Dieser Artikel hilft neuen Community-Mitgliedern, ihre eigenen Kerzenmuster zu suchen und zu entdecken. Die Beschreibung dieser Muster kann entmutigend sein, da sie eine manuelle Suche und kreative Identifizierung von Verbesserungen erfordert. Hier stellen wir die Engulfing-Kerzen vor und zeigen, wie es für profitablere Handelsanwendungen verbessert werden kann.