Bivariate Copulae in MQL5 (Teil 2): Implementierung archimedischer Copulae in MQL5
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.
![]()
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:

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:

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.

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:

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:
![]()
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.

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:
![]()
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:
![]()
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.

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:

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:
![]()
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.

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:

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.

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:

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.

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.

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:

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.

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.

Ü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.

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).

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.


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.

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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
MetaTrader 5 Machine Learning Blueprint (Teil 5): Sequentielles Bootstrapping – Verzicht auf Kennzeichen, Verbesserung der Ergebnisse
Blaupause für maschinelles Lernen (Teil 4): Die versteckte Schwachstelle in Ihrer ML-Pipeline – Gleichzeitigkeit der Kennzeichnungen
Markets Positioning Codex in MQL5 (Teil 1): Bitwise Learning für Nvidia
Klassische Strategien neu interpretieren (Teil 18): Suche nach Kerzenmustern
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.