English Русский 中文 Español 日本語 Português
preview
Kausalanalyse von Zeitreihen mit Hilfe der Transferentropie

Kausalanalyse von Zeitreihen mit Hilfe der Transferentropie

MetaTrader 5Beispiele | 23 September 2024, 11:15
178 0
Francis Dube
Francis Dube

Einführung

Die Transferentropie ist ein statistisches Instrument, das die Menge an Informationen quantifiziert, die von einer Zeitreihe auf eine andere übertragen wird, und Einblicke in die Art und das Verhalten einer Zielvariablen gewährt. In diesem Artikel befassen wir uns mit dem Konzept der statistischen Kausalität, die anhand der Transferentropie berechnet wird. Wir untersuchen, wie diese Methode die Richtung des kausalen Einflusses zwischen verschiedenen Prozessen aufzeigen kann. Darüber hinaus wird eine detaillierte Beschreibung einer MQL5-Implementierung zur Messung der Transferentropie gegeben, die zeigt, wie diese Technik praktisch zur Analyse potenziell gekoppelter Zeitreihen eingesetzt werden kann. Durch die Nutzung der Transferentropie wollen wir Variablen identifizieren, die Vorhersageaufgaben verbessern können.


Kausalität

Empirische Daten können trügerisch sein. Nur weil sich zwei Variablen scheinbar parallel zueinander bewegen, heißt das nicht, dass die eine die andere verursacht. Deshalb gilt der Spruch „Korrelation ist keine Kausalität“. Die Korrelation misst lediglich, wie zwei Variablen miteinander verbunden sind, nicht warum sie miteinander verbunden sind. Stellen Sie sich zum Beispiel eine starke Korrelation zwischen dem Verkauf von Speiseeis und dem Aktienkurs im Sommer vor. Das bedeutet nicht, dass der Kauf von Eiscreme die Aktie steigen lässt! Wahrscheinlicher ist ein versteckter Faktor, wie die Jahreszeit selbst, der beide Variablen unabhängig voneinander beeinflusst. Ebenso kann ein Zusammenhang zwischen den Aktien eines Unternehmens und den Goldpreisen bestehen, aber die eigentliche Ursache könnte etwas ganz anderes sein, wie die allgemeine Marktstimmung oder die Inflation, die beide Preise beeinflusst. Diese Beispiele zeigen, dass korrelierte Daten irreführend sein können. Sie zeigen eine Verbindung, aber nicht den Grund dafür. Um wirklich zu verstehen, ob eine Sache eine andere verursacht, brauchen wir fortschrittlichere Instrumente.

Pendel

Das Konzept der Kausalität, also die Vorstellung, dass ein Ereignis ein anderes hervorruft, ist für die wissenschaftliche Forschung von grundlegender Bedeutung. Die genaue Definition von Kausalität ist jedoch eine vielschichtige Herausforderung mit tiefgreifenden philosophischen, physikalischen und statistischen Überlegungen. Im Idealfall würde eine Ursache immer eine einzige Wirkung hervorrufen. Es kann jedoch schwierig sein, einen einzelnen kausalen Faktor aus dem oft komplexen Geflecht von Einflüssen zu isolieren, die sich auf ein Ergebnis auswirken. So kann beispielsweise ein Anstieg des Handelsvolumens mit einem Anstieg des Aktienkurses korrelieren, aber auch andere Faktoren wie die Marktstimmung und die Veröffentlichung von Wirtschaftsdaten können eine wichtige Rolle spielen. In solchen Szenarien verwenden die Forscher statistische Verfahren, um kausale Zusammenhänge abzuleiten.

Die Art der Kausalität, ob deterministisch (garantiertes Ergebnis) oder probabilistisch (Beeinflussung der Wahrscheinlichkeit des nachfolgenden Ereignisses), hängt von dem zugrunde liegenden Prozess ab. In deterministischen Systemen führt das erste Ereignis nachweislich zum zweiten, wie beim vorhersehbaren Fall eines fallen gelassenen Gegenstandes zu beobachten ist. Bei probabilistischen Systemen hingegen liegt der Schwerpunkt darauf, ob das erste Ereignis unsere Fähigkeit verbessert, das Eintreten des zweiten Ereignisses vorherzusagen. So könnten beispielsweise die jüngsten Regenfälle mit der späteren Blüte der Blumen in Verbindung gebracht werden, aber auch andere Umweltfaktoren könnten dazu beitragen. In solchen Fällen stellt sich die Frage, ob die Kenntnis des ersten Ereignisses unsere Fähigkeit verbessert, das zweite Ereignis vorherzusagen.

Der Wirtschaftswissenschaftler Clive Granger entwickelte, aufbauend auf der Arbeit von Norbert Wiener, das Konzept der Kausalbeziehungen und stellte die These auf, dass ein erstes Signal ein zweites Signal verursacht, wenn die zukünftigen Werte des zweiten Signals besser erklärt werden können, wenn man die Informationen aus der Vergangenheit sowohl des ersten als auch des zweiten Signals verwendet, als wenn man nur die verzögerten Werte des zweiten Signals allein verwendet. Granger stützte seine Definition der Kausalität auf zwei Prinzipien. Erstens kann sich eine Wirkung nicht vor ihrer Ursache manifestieren. Zweitens enthält eine Ursache einzigartige Informationen, die auf die Wirkung übertragen werden. Diese Grundsätze legen nahe, dass wir zur Quantifizierung der Kausalität die zeitlichen Eigenschaften der beteiligten Variablen sowie ein gewisses Maß an deren Informationsgehalt verstehen müssen. Daher eignen sich Zeitreihen gut für die Kausalanalyse.

Ursache und Wirkung

Aufgrund der Beschaffenheit von Zeitreihendaten können wir analysieren, wie Informationen aus einer Reihe zu einem bestimmten Zeitpunkt die Vorhersagbarkeit einer anderen Reihe zu einem späteren Zeitpunkt beeinflussen. Granger definiert Kausalität als eine Verringerung der Unsicherheit. Wenn die Kenntnis der Vergangenheitswerte der Reihe X unsere Vorhersage des zukünftigen Wertes der Reihe Y im Vergleich zu den Vergangenheitswerten von Y selbst verbessert, dann wird gesagt, dass X für Y prädiktiv ist. Auf der Grundlage dieser Idee entwickelte Granger Tests für Kausalität unter Verwendung verzögerter Zeitreihen und autoregressiver Modelle. Er schlug vor, dass es keine kausale Beziehung zwischen X und Y gibt, es sei denn, die Einbeziehung vergangener Werte von X verbessert die Vorhersage zukünftiger Werte von Y erheblich. Diese Verbesserung wird in der Regel durch eine Verringerung des Vorhersagefehlers gemessen, beispielsweise durch eine bessere Anpassung eines Regressionsmodells. Mit Hilfe der Granger-Kausalität lassen sich Beziehungen zwischen Zeitreihen statistisch nachweisen. Es ist jedoch wichtig, seine Grenzen zu beachten. Die Granger-Kausalität zeigt nur eine gerichtete Beziehung auf, nicht unbedingt einen endgültigen Kausalmechanismus. Es könnten auch andere Ursachen im Spiel sein, als die, über die wir Daten haben. Da die Granger-Kausalität im Wesentlichen auf dem Rahmen der Autoregression aufbaut, ist sie zudem am effektivsten bei der Aufdeckung linearer Kausalbeziehungen. Die nichtlineare Kausalität erfordert einen anderen Ansatz.

Mathematisch lässt sich die Granger-Kausalität durch die Betrachtung zweier Zeitreihen, X und Y, ausdrücken. Die verzögerten Werte beider Reihen werden mit X(t-k) und Y(t-k) bezeichnet, was eine Verzögerung bei k darstellt. Die maximale Verzögerung wird mit p bezeichnet. Bei der Anwendung des Autoregressionsmodells wird der zukünftige Wert von Y auf seine eigenen vergangenen Werte regressiert.

Autoregressionsformel

Dieser Ausdruck berücksichtigt den zukünftigen Wert von Y nur in Bezug auf seine vergangenen Werte. Durch die Einführung von X in das Modell werden die zukünftigen Werte in Form von Vergangenheitswerten von X und Y ausgedrückt.

Vektorielle Autoregressionsformel

Wenn die Einbeziehung der Vergangenheitswerte von X die Vorhersage von Y im Vergleich zu einem Modell, das nur die Vergangenheitswerte von Y verwendet, signifikant verbessert, dann wird gesagt, dass X eine Granger-Ursache für Y ist. Dies wird in der Regel durch Testen der Nullhypothese, dass die Koeffizienten gemeinsam Null sind, beurteilt. Wenn diese Nullhypothese abgelehnt wird, bedeutet dies, dass X signifikante Vorhersageinformationen über Y liefert, die über das hinausgehen, was allein in den Vergangenheitswerten von Y enthalten ist. Um zu prüfen, ob X eine Granger-Ursache für Y ist, vergleichen wir die beiden Modelle im Rahmen des Hypothesentests:

  • Null-Hypothese: X hat keine Granger-Ursache für Y.
  • Alternative Hypothese: X ist die Ursache von Y.

Ein F-Test wird verwendet, um die Anpassung des eingeschränkten Modells (ohne X) und des uneingeschränkten Modells (mit X) zu vergleichen, indem die Residuen der jeweiligen Modelle untersucht werden. Die eingeschränkte Summe der quadrierten Residuen sind die Residuen aus dem Modell ohne X, und die unbeschränkte Summe der quadrierten Residuen stammt aus dem Modell mit X. Die F-Statistik wird wie folgt berechnet:

F-Statistik-Formel

Dabei ist n die Anzahl der Beobachtungen. Die berechnete F-Statistik wird mit dem kritischen Wert aus der F-Verteilung mit p und n - 2p - 1 Freiheitsgraden verglichen. Ist die F-Statistik größer als der kritische Wert, wird die Nullhypothese zurückgewiesen und die Schlussfolgerung gezogen, dass X die Ursache für Y ist. Alternativ kann die F-Statistik auch mit Hilfe einer einseitigen Varianzanalyse (ANOVA) berechnet werden. Deren Formel ist unten angegeben.

ANOVA-basierte Granger-Kausalität

Übertragung der Entropie

In den Anfängen der Informationstheorie nutzten die Wissenschaftler die wechselseitige Information, um zu verstehen, wie gekoppelte Prozesse zusammenwirken. Dieses Konzept, das auf der Entropie von Claude Shannon beruht, gibt Aufschluss darüber, ob sich die Informationen in einer Zeitreihe mit denen einer anderen überschneiden. Einfacher ausgedrückt: Es zeigt, ob wir beide Reihen zusammen mit weniger Informationen kodieren können als wenn wir sie getrennt kodieren. Aus diesem Grund wird die wechselseitige Information manchmal auch als Redundanz bezeichnet. Ein Prozess teilt Informationen mit einem anderen, sodass der zweite Prozess effizient beschrieben werden kann, indem Informationen, die bereits vom ersten Prozess erfasst wurden, wiederverwendet werden.

Formal ist die wechselseitige Information zwischen zwei stochastischen Prozessen, X(t) und Y(t), gegeben, wenn die Summe ihrer Randentropien die gemeinsame Entropie des kombinierten Systems übersteigt. Diese mathematische Beziehung spiegelt die Verringerung der Unsicherheit des kombinierten Systems im Vergleich zu den einzelnen Prozessen wider. Mit anderen Worten: Sie gibt an, inwieweit Informationen über einen Prozess genutzt werden können, um die dem anderen Prozess innewohnende Entropie zu verringern. Da die Entropie ausschließlich durch die zugrunde liegende Wahrscheinlichkeitsverteilung bestimmt wird, kann jede solche Verteilung durch einen zugehörigen Entropiewert charakterisiert werden. Dieser Wert quantifiziert den Grad der Unerwartetheit, der mit einem bestimmten Ergebnis verbunden ist, angesichts der bekannten Wahrscheinlichkeitsverteilung.

Dieses Konzept ist insbesondere im Zusammenhang mit der Granger-Kausalität von Bedeutung. Bei der Untersuchung potenzieller kausaler Beziehungen zwischen Zeitreihen besteht das Ziel darin, die mit einem Zielprozess verbundene Unsicherheit durch Einbeziehung von Informationen aus einem potenziellen Quellprozess zu verringern. Wenn die Einbeziehung einer sekundären Zeitreihe nachweislich die Entropie der Verteilung des Zielprozesses verringert, deutet dies auf das Vorhandensein eines statistischen kausalen Einflusses von der Quellreihe auf die Zielreihe hin. Diese Verringerung wird als Transferentropie bezeichnet.


Die Transferentropie (TE) baut auf dem Konzept der Kullback-Leibler-Divergenz auf und misst die Richtung der Informationsübertragung zwischen zwei Zeitreihen. Die TE basiert auf der Idee der bedingten wechselseitigen Information und kann durch den Kullback-Leibler (KL)-Abstand, auch bekannt als Kullback-Leibler-Divergenz oder relative Entropie, ausgedrückt werden. Die KL-Divergenz misst die Differenz zwischen zwei Wahrscheinlichkeitsverteilungen. In TE misst der KL-Abstand die Differenz zwischen der gemeinsamen Wahrscheinlichkeitsverteilung des aktuellen Zustands von Y und der vergangenen Zustände von X und Y und dem Produkt der Randverteilungen dieser Zustände. Mathematisch lässt sich die Übertragung von Informationen von einer Zeitreihe X auf eine Zeitreihe Y wie folgt ausdrücken:

Formel der Transferentropie



wobei y(t+1) der zukünftige Zustand von Y ist, y(t) der vergangene Zustand von Y und x(t) der vergangene Zustand von X ist. Diese Formulierung macht deutlich, dass die Transferentropie misst, wie stark die Wahrscheinlichkeitsverteilung von y(t+1) sich ändert, wenn zusätzlich zu y(t) auch die Informationen von x(t) berücksichtigt werden.

Im Jahr 2009 haben Lionel Barnett, Adam Barrett und Anil Seth gemeinsam die Publikation: "Granger Causality and Transfer Entropy Are Equivalent for Gaussian Variables" verfasst, in der gezeigt wird, dass bei Zeitreihen, die einer Gauß-Verteilung folgen, die Transferentropie der Hälfte der F-Statistik für Granger-Kausalität entspricht.

Kausalität im Sinne der Transferentropieformel

Dieses Ergebnis liefert die Definition der linearen Transferentropie, die wir später in Code umsetzen werden. Um die nichtlineare Kausalität zu berücksichtigen, erweitern wir das Konzept der Unsicherheitsreduktion in Anlehnung an die Arbeit von Thomas Schreiber, der die Zeitreihen als Markov-Prozess mit unterschiedlichen Übergangswahrscheinlichkeitsverteilungen behandelt.

Schreibers Ansatz zur Modellierung der Unsicherheitsreduktion nutzt die Informationstheorie, indem er die Zeitreihen X(t) und Y(t) als Markov-Prozesse mit bekannten Übergangswahrscheinlichkeiten p(x) und q(x) behandelt. Im Gegensatz zum autoregressiven Modell von Granger, das sich auf lineare Modelle stützt, verwendet dieser Ansatz die bedingte wechselseitige Information, um die Informationsübertragung zu beschreiben. Da die wechselseitige Information aus der Differenz der Entropien abgeleitet wird, erhält man die bedingte wechselseitige Information, indem man jeden Entropieterm auf zusätzliche Informationen konditioniert. Die Transferentropie wird dann berechnet, indem die verzögerten Variablen in die Gleichung der bedingten wechselseitigen Information eingesetzt werden, sodass der Informationstransfer von X(t) nach Y(t) zu einer bestimmten Verzögerung k unter Verwendung der bedingten wechselseitigen Entropie analysiert werden kann.

Gemeinsame bedingte Entropie

Unabhängige bedingte Entropie

Rechnerisch ist diese Methode attraktiv, weil die gemeinsame Entropie nur eine Wahrscheinlichkeitsverteilung erfordert. Die Transferentropie für eine einzelne Verzögerung k kann als vier getrennte gemeinsame Entropieterme ausgedrückt werden, die sich leicht mit einer genauen Wahrscheinlichkeitsverteilung aus den Daten berechnen lassen. Der Vorteil dieser Formel ist, dass sie mehr verzögerte Dimensionen verarbeiten kann. Jede zusätzliche Verzögerung erhöht jedoch die Dimensionalität des Zustandsraums um zwei, was die Fähigkeit zur genauen Quantifizierung der Transferentropie aufgrund des exponentiellen Wachstums der endlichen Daten, die mit der Schätzung der Wahrscheinlichkeitsdichten verbunden sind, erheblich beeinträchtigt.

Nicht lineare Entropie-Terme

Eine wesentliche Stärke dieses Ansatzes liegt in seiner nicht-parametrischen Natur. Im Gegensatz zu anderen Methoden werden keine über die Stationarität hinausgehenden Annahmen über die zugrundeliegende Datenverteilung getroffen, sodass eine Anwendung ohne vorherige Kenntnis der datenerzeugenden Prozesse möglich ist. Dieser Vorteil ist jedoch mit einer Einschränkung verbunden: Die Ergebnisse hängen in hohem Maße von einer genauen Schätzung der zugrunde liegenden Verteilung ab. Die Transferentropie erfordert eine Annäherung an die tatsächliche Wahrscheinlichkeitsverteilung der beteiligten stochastischen Prozesse, wobei begrenzte Daten zur Berechnung der vier Entropieterme verwendet werden. Die Genauigkeit dieser Schätzung hat erhebliche Auswirkungen auf die Zuverlässigkeit der Ergebnisse der Transferentropie. Vor diesem Hintergrund muss man die Möglichkeit in Betracht ziehen, dass die berechneten Entropiewerte verfälscht sein könnten. Daher müssen wir irgendwie die Robustheit der Ergebnisse ermitteln.

Unser Beharren auf der Verwendung eines nicht-parametrischen Ansatzes zur Schätzung der Transferentropie ist mit der beträchtlichen Herausforderung verbunden, sicherzustellen, dass die Ergebnisse einen gewissen Wahrheitsgehalt haben und nicht nur Müll sind. Daher ist es sinnvoll, einen aussagekräftigeren Ansatz für die Interpretation der Transferentropie in Betracht zu ziehen, der die Bewertung der statistischen Signifikanz des geschätzten Wertes beinhaltet. Bei den üblichen Signifikanztests werden die Zeitreihendaten eine vordefinierte Anzahl von Malen gemischt. Die Transferentropie wird dann für jede gemischte (shuffled) Version berechnet. Der p-Wert wird anschließend als der Anteil der gemischten Daten berechnet, deren Übertragungsentropie niedriger ist als der ursprüngliche Wert.

Bei einem anderen Ansatz muss man die Anzahl der Standardabweichungen berechnen, um die ein Ergebnis vom Mittelwert der gemischten Daten abweicht. Da das Mischen die zeitliche Struktur unterbricht, wird erwartet, dass der Mittelwert der Werte für die gemischte Transferentropie nahe bei Null liegt. Die Streuung der Daten um diesen Mittelwert spiegelt die Bedeutung des ursprünglichen Ergebnisses wider. Der berechnete Wert wird als z-Score bezeichnet. z-Scores erfordern im Vergleich zu p-Werten in der Regel weniger Mischungen, was sie rechnerisch effizienter macht. 

Im Falle des p-Wertes ist es das Ziel, eine Wahrscheinlichkeit zu erhalten, die so nahe wie möglich bei Null liegt. Ein z-Score, der eine statistische Signifikanz anzeigt, sollte über 3,0 liegen.


MQL5-Implementierung

Der Code, der die Instrumente zur Quantifizierung der Transferentropie und zur Bestimmung ihrer Bedeutung implementiert, ist in transfer_entropy.mqh enthalten. Die Datei enthält die Definition der Klasse CTransEntropy sowie weitere Hilfsklassen und Funktionen. Dieser Kurs bietet einen Rahmen für die statistische Analyse von Zeitreihendaten, der speziell auf die Bewertung von Kausalbeziehungen zwischen Variablen ausgerichtet ist. Es werden zwei unterschiedliche Methoden zur Quantifizierung der linearen Granger-Kausalität (lineare Transferentropie) und der nichtlinearen Transferentropie vorgestellt. Sie wird in beide Richtungen berechnet und vermittelt ein vollständigeres Bild des Informationsflusses zwischen den Variablen.

Um einer möglichen Nicht-Stationarität in den Daten entgegenzuwirken, wird in der Klasse ein Fensterverfahren eingesetzt. Der Nutzer kann die Fenstergröße und die Schrittweite festlegen, sodass die Daten in kleineren, sich überschneidenden Segmenten analysiert werden können. Dieser Ansatz liefert für jedes Fenster spezifische Ergebnisse und erleichtert die Identifizierung zeitlicher Schwankungen der Kausalitätsstärke. Darüber hinaus werden die mit der Analyse von nicht-stationären Daten verbundenen Herausforderungen gemildert. Die Klasse bietet auch einen integrierten Mechanismus für Signifikanztests. Die Nutzer können die Anzahl der durchzuführenden Datenmischungen angeben, wobei die Randverteilungen erhalten bleiben. Auf der Grundlage dieser gemischten Datensätze berechnet die Klasse p-Werte und z-Scores für die Transferentropie in jeder Richtung. Diese statistischen Werte geben Aufschluss über die Wahrscheinlichkeit, dass die beobachteten kausalen Beziehungen oder der Informationstransfer auf Zufall beruhen, und erhöhen so die Robustheit der Analyse.

Eine Instanz der Klasse wird mit dem parameterlosen Standardkonstruktor instanziiert.

public:
                     CTransEntropy(void)
     {
      if(!m_transfer_entropies.Resize(2))
         Print(__FUNCTION__, " error ", GetLastError());

     }

Die Nutzer sollten dann die Methode Initialize() aufrufen, die das Objekt mit einem bestimmten Datensatz initialisiert und verschiedene Parameter für die Analyse einrichtet.

bool              Initialize(matrix &in, ulong endog_index, ulong exog_index, ulong lag, bool maxLagOnly=true, ulong winsize=0,ulong winstride=0)
     {
      if(!lag || lag>in.Rows()/2)
        {
         Print(__FUNCTION__, " Invalid parameter(s) : lag must be > 0  and < rows/2");
         return false;
        }

      if(endog_index==exog_index)
        {
         Print(__FUNCTION__, " Invalid parameter(s) : endog cannot be = exog ");
         return false;
        }

      if(!m_dataset.Resize(in.Rows(),2))
        {
         Print(__FUNCTION__, " error ", GetLastError());
         return false;
        }

      if(!m_dataset.Col(in.Col(endog_index),0) || !m_dataset.Col(in.Col(exog_index),1))
        {
         Print(__FUNCTION__, " error ", GetLastError());
         return false;
        }

      if(!m_wins.Initialize(m_dataset,lag,maxLagOnly,winsize,winstride))
         return false;

      m_tlag = lag;
      m_endog = endog_index;
      m_exog = exog_index;
      m_maxlagonly = maxLagOnly;

      return true;
     }

Der erste erforderliche Parameter ist eine Matrix mit mindestens zwei Spalten, wobei die zu analysierenden Zeitreihen in den Spalten der Eingabematrix platziert werden sollten. Bei nicht-stationären Daten empfiehlt es sich, die Daten vorher zu differenzieren. Der zweite und dritte Parameter sind die Spaltenindizes der Eingabedatenmatrix, die die endogenen (abhängigen) Zeitreihen bzw. die exogenen (unabhängigen) Zeitreihen angeben.

Der vierte Parameter, lag, definiert den in der Analyse berücksichtigten Lag-Parameter. Der nächste boolesche Parameter, maxLagOnly, bestimmt, ob lag einen einzelnen Term definiert (wenn true) oder alle verzögerten Werte bis einschließlich lag (wenn false). Der vorletzte Parameter, winsize, gibt die Länge des Fensters an. Ist dieser Wert auf 0 gesetzt, wird keine Fensterung auf die Daten angewendet. Schließlich legt winstride optional die Schrittweite des Fensters für Fensteroperationen fest, d. h. den Schritt zwischen aufeinanderfolgenden Fenstern, wenn sie über die Zeitreihendaten laufen.

Die Methode beginnt damit, dass sichergestellt wird, dass die endogenen und exogenen Indizes nicht identisch sind. Ist dies der Fall, wird eine Fehlermeldung ausgegeben und false zurückgegeben. Die interne Matrix m_dataset wird in ihrer Größe angepasst, um den zu analysierenden bivariaten Datensatz zu speichern. Anschließend werden die durch endog_index und exog_index angegebenen Spalten aus der Eingabematrix in die erste bzw. zweite Spalte von m_dataset kopiert. Wenn Fensterung gewünscht ist, wird die Hilfsklasse CDataWindows verwendet, um die m_dataset-Matrix zu fenstern. Sobald dies geschehen ist, setzt die Methode interne Variablen mit den angegebenen Parametern zur späteren Verwendung.

//+------------------------------------------------------------------+
//|class that generates windows of the dataset to be analyzed        |
//+------------------------------------------------------------------+
class CDataWindows
  {
private:
   matrix m_dwins[],
          m_data;
   ulong  m_lag,
          m_win_size,
          m_stride_size;

   bool m_max_lag_only,
        m_has_windows;

   matrix            applylags(void)
     {
      matrix out=np::sliceMatrixRows(m_data,m_lag);

      if(m_max_lag_only)
        {
         if(!out.Resize(out.Rows(),m_data.Cols()+2))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return matrix::Zeros(1,1);
           }

         for(ulong i = 2; i<4; i++)
           {
            vector col = m_data.Col(i-2);
            col = np::sliceVector(col,0,col.Size()-m_lag);

            if(!out.Col(col,i))
              {
               Print(__FUNCTION__, " error ", GetLastError());
               return matrix::Zeros(1,1);
              }
           }
        }
      else
        {
         if(!out.Resize(out.Rows(),m_data.Cols()+(m_lag*2)))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return matrix::Zeros(1,1);
           }

         for(ulong i = 0,k = 2; i<2; i++)
           {
            for(ulong t = 1; t<(m_lag+1); t++,k++)
              {
               vector col = m_data.Col(i);
               col = np::sliceVector(col,m_lag-t,col.Size()-t);

               if(!out.Col(col,k))
                 {
                  Print(__FUNCTION__, " error ", GetLastError());
                  return matrix::Zeros(1,1);
                 }
              }

           }
        }

      return out;

     }

   bool              applywindows(void)
     {
      if(m_dwins.Size())
         ArrayFree(m_dwins);

      for(ulong i = (m_stride_size+m_win_size); i<m_data.Rows(); i+=ulong(MathMax(m_stride_size,1)))
        {
         if(ArrayResize(m_dwins,int(m_dwins.Size()+1),100)<0)
           {
            Print(__FUNCTION__," error ", GetLastError());
            return false;
           }
         m_dwins[m_dwins.Size()-1] = np::sliceMatrixRows(m_data,i-m_win_size,(i-m_win_size)+m_win_size);
        }

      return true;
     }


public:
                     CDataWindows(void)
     {

     }

                    ~CDataWindows(void)
     {

     }

   bool              Initialize(matrix &data, ulong lag, bool max_lag_only=true, ulong window_size=0, ulong window_stride =0)
     {
      if(data.Cols()<2)
        {
         Print(__FUNCTION__, " matrix should contain at least 2 columns ");
         return false;
        }

      m_data = data;

      m_max_lag_only = max_lag_only;

      if(lag)
        {
         m_lag = lag;
         m_data = applylags();
        }

      if(window_size)
        {
         m_win_size = window_size;
         m_stride_size = window_stride;
         m_has_windows = true;
         if(!applywindows())
            return false;
        }
      else
        {
         m_has_windows = false;

         if(m_dwins.Size())
            ArrayFree(m_dwins);

         if(ArrayResize(m_dwins,1)<0)
           {
            Print(__FUNCTION__," error ", GetLastError());
            return false;
           }

         m_dwins[0]=m_data;
        }

      return true;
     }

   matrix            getWindowAt(ulong ind)
     {
      if(ind < ulong(m_dwins.Size()))
         return m_dwins[ind];
      else
        {
         Print(__FUNCTION__, " Index out of bounds ");
         return matrix::Zeros(1,1);
        }
     }

   ulong             numWindows(void)
     {
      return ulong(m_dwins.Size());
     }

   bool              hasWindows(void)
     {
      return m_has_windows;
     }
  };

Wenn die Methode Initialize() erfolgreich abgeschlossen wurde, kann der Nutzer entweder Calculate_Linear_TE() oder Calculate_NonLinear_TE() aufrufen, um die lineare bzw. nichtlineare Transferentropie zu testen. Beide Methoden geben nach Abschluss einen booleschen Wert zurück. Die Methode Calculate_Linear_TE() kann einen einzigen optionalen Parameter, n_shuffles, annehmen. Wenn n_shuffles gleich Null ist (Standardeinstellung), werden keine Signifikanztests durchgeführt.

bool              Calculate_Linear_TE(ulong n_shuffles=0)
     {
      ulong c = m_wins.numWindows();

      matrix TE(c,2);
      matrix sTE(c,2);
      matrix pvals(c,2);
      matrix zscores(c,2);

      for(ulong i=0; i<m_wins.numWindows(); i++)
        {
         matrix df = m_wins.getWindowAt(i);

         m_transfer_entropies[0] = linear_transfer(df,0,1);

         m_transfer_entropies[1] = linear_transfer(df,1,0);


         if(!TE.Row(m_transfer_entropies,i))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return false;
           }

         SigResult rlts;

         if(n_shuffles)
           {
            significance(df,m_transfer_entropies,m_endog,m_exog,m_tlag,m_maxlagonly,n_shuffles,rlts);

            if(!sTE.Row(rlts.mean,i) || !pvals.Row(rlts.pvalue,i) || !zscores.Row(rlts.zscore,i))
              {
               Print(__FUNCTION__, " error ", GetLastError());
               return false;
              }

           }

        }

      m_results.TE_XY = TE.Col(0);
      m_results.TE_YX = TE.Col(1);
      m_results.p_value_XY = pvals.Col(0);
      m_results.p_value_YX = pvals.Col(1);
      m_results.z_score_XY = zscores.Col(0);
      m_results.z_score_YX = zscores.Col(1);
      m_results.Ave_TE_XY = sTE.Col(0);
      m_results.Ave_TE_YX = sTE.Col(1);

      return true;
     }

Die Methode berechnet die lineare Transferentropie nach der Grangers-Methode. Dies ist in der privaten Methode linear_transfer() implementiert. Die letzten beiden Parameter dieser Routine identifizieren die abhängige und unabhängige Variable (Spalte) in der Eingabematrix. Durch zweimaligen Aufruf der Methode mit vertauschten Spaltenindizes erhalten wir die Transferentropie in beide Richtungen.

double            linear_transfer(matrix &testdata,long dep_index, long indep_index)
     {
      vector joint_residuals,independent_residuals;
      double entropy=0.0;

      OLS ols;

      double gc;
      vector y;
      matrix x,xx;

      matrix joint;
      if(m_maxlagonly)
         joint = np::sliceMatrixCols(testdata,2);
      else
        {
         if(!joint.Resize(testdata.Rows(), testdata.Cols()-1))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }
         matrix sliced = np::sliceMatrixCols(testdata,2);
         if(!np::matrixCopyCols(joint,sliced,1) || !joint.Col(testdata.Col(indep_index),0))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }
        }
      matrix indep = (m_maxlagonly)?np::sliceMatrixCols(testdata,dep_index+2,dep_index+3):np::sliceMatrixCols(testdata,(dep_index==0)?2:dep_index+m_tlag+1,(dep_index==0)?2+m_tlag:END);

      y = testdata.Col(dep_index);

      if(dep_index>indep_index)
        {
         if(m_maxlagonly)
           {
            if(!joint.SwapCols(0,1))
              {
               Print(__FUNCTION__, " error ", GetLastError());
               return entropy;
              }
           }
         else
           {
            for(ulong i = 0; i<m_tlag; i++)
              {
               if(!joint.SwapCols(i,i+m_tlag))
                 {
                  Print(__FUNCTION__, " error ", GetLastError());
                  return entropy;
                 }
              }
           }
        }

      if(!addtrend(joint,xx))
         return entropy;

      if(!ols.Fit(y,xx))
         return entropy;

      joint_residuals = ols.Residuals();

      if(!addtrend(indep,x))
         return entropy;

      if(!ols.Fit(y,x))
         return entropy;

      independent_residuals = ols.Residuals();

      gc = log(independent_residuals.Var()/joint_residuals.Var());

      entropy = gc/2.0;

      return entropy;

     }

Die Methode Calculate_NonLinear_TE() nimmt neben n_shuffles einen zusätzlichen Parameter, numBins, entgegen. Dieser Parameter legt die Anzahl der Bins fest, die bei der Schätzung der Wahrscheinlichkeitsdichte der Variablen verwendet werden. 

bool              Calculate_NonLinear_TE(ulong numBins, ulong n_shuffles=0)
     {
      ulong c = m_wins.numWindows();

      matrix TE(c,2);
      matrix sTE(c,2);
      matrix pvals(c,2);
      matrix zscores(c,2);

      for(ulong i=0; i<m_wins.numWindows(); i++)
        {
         matrix df = m_wins.getWindowAt(i);

         m_transfer_entropies[0] = nonlinear_transfer(df,0,1,numBins);

         m_transfer_entropies[1] = nonlinear_transfer(df,1,0,numBins);


         if(!TE.Row(m_transfer_entropies,i))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return false;
           }

         SigResult rlts;

         if(n_shuffles)
           {
            significance(df,m_transfer_entropies,m_endog,m_exog,m_tlag,m_maxlagonly,n_shuffles,rlts,numBins,NONLINEAR_TE);

            if(!sTE.Row(rlts.mean,i) || !pvals.Row(rlts.pvalue,i) || !zscores.Row(rlts.zscore,i))
              {
               Print(__FUNCTION__, " error ", GetLastError());
               return false;
              }

           }

        }

      m_results.TE_XY = TE.Col(0);
      m_results.TE_YX = TE.Col(1);
      m_results.p_value_XY = pvals.Col(0);
      m_results.p_value_YX = pvals.Col(1);
      m_results.z_score_XY = zscores.Col(0);
      m_results.z_score_YX = zscores.Col(1);
      m_results.Ave_TE_XY = sTE.Col(0);
      m_results.Ave_TE_YX = sTE.Col(1);

      return true;


     }

Zur Schätzung der Wahrscheinlichkeitsdichte wird die Histogramm-Methode verwendet. Sie wurde gewählt, weil sie am einfachsten zu implementieren ist. Die Verantwortung für die Berechnung der verallgemeinerten Version der Transferentropie wird an die privaten Methoden nonlinear_entropy() und get_entropy() delegiert. 

double            get_entropy(matrix &testdata, ulong num_bins)
     {

      vector hist;
      vector bounds[];
      hist=vector::Ones(10);

      if(!np::histogramdd(testdata,num_bins,hist,bounds))
        {
         Print(__FUNCTION__, " error ");
         return EMPTY_VALUE;
        }

      vector pdf = hist/hist.Sum();
      vector lpdf = pdf;

      for(ulong i = 0; i<pdf.Size(); i++)
        {
         if(lpdf[i]==0.0)
            lpdf[i] = 1.0;
        }

      vector ent = pdf*log(lpdf);

      return -1.0*ent.Sum();

     }

Die vier Komponentenwerte, die zur Berechnung der gemeinsamen und unabhängigen bedingten Entropien verwendet werden, werden in nonlinear_transfer() kombiniert, um die endgültige Schätzung zu erhalten.

double            nonlinear_transfer(matrix &testdata,long dep_index, long indep_index, ulong numbins)
     {
      double entropy=0.0;

      matrix one;
      matrix two;
      matrix three;
      matrix four;

      if(m_maxlagonly)
        {
         if(!one.Resize(testdata.Rows(),3) || !two.Resize(testdata.Rows(),2) || !three.Resize(testdata.Rows(),2) || !four.Resize(testdata.Rows(),1) ||
            !one.Col(testdata.Col(dep_index),0) || !one.Col(testdata.Col(dep_index+2),1) || !one.Col(testdata.Col(indep_index+2),2) ||
            !two.Col(testdata.Col(indep_index+2),0) || !two.Col(testdata.Col(dep_index+2),1) ||
            !three.Col(testdata.Col(dep_index),0) || !three.Col(testdata.Col(dep_index+2),1) ||
            !four.Col(testdata.Col(dep_index),0))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }
        }
      else
        {

         if(!one.Resize(testdata.Rows(), testdata.Cols()-1) || !two.Resize(testdata.Rows(), testdata.Cols()-2) ||
            !three.Resize(testdata.Rows(), m_tlag+1))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }

         matrix deplag = np::sliceMatrixCols(testdata,dep_index?dep_index+m_tlag+1:2,dep_index?END:2+m_tlag);
         matrix indlag = np::sliceMatrixCols(testdata,indep_index?indep_index+m_tlag+1:2,indep_index?END:2+m_tlag);
         //one
         if(!np::matrixCopyCols(one,deplag,1,1+m_tlag) || !np::matrixCopyCols(one,indlag,1+m_tlag) || !one.Col(testdata.Col(dep_index),0))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }
         //two
         if(!np::matrixCopyCols(two,indlag,indlag.Cols()) || !np::matrixCopyCols(two,deplag,indlag.Cols()))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }
         //three
         if(!np::matrixCopyCols(three,deplag,1) || !three.Col(testdata.Col(dep_index),0))
           {
            Print(__FUNCTION__, " error ", GetLastError());
            return entropy;
           }
         //four
         four = deplag;
        }

      double h1=get_entropy(one,numbins);
      double h2=get_entropy(two,numbins);
      double h3=get_entropy(three,numbins);
      double h4=get_entropy(four,numbins);

      // entropy = independent conditional entropy (h3-h4)  - joint conditional entropy (h1-h2)
      entropy = (h3-h4) - (h1-h2);

      return entropy;

     }

Umfassende Ergebnisse des Tests können mit der Methode get_results() abgerufen werden, die eine Struktur von Vektoren zurückgibt. Jedes Element dieser Struktur bezieht sich auf einen anderen Aspekt der Ergebnisse, wobei die Länge jedes Vektors von den Parametern abhängt, die mit der Methode Initialize() festgelegt wurden, sowie von der Art der durchgeführten Transferentropieanalyse.

//+------------------------------------------------------------------+
//| Transfer entropy results struct                                  |
//+------------------------------------------------------------------+
struct TEResult
  {
   vector            TE_XY;
   vector            TE_YX;
   vector            p_value_XY;
   vector            p_value_YX;
   vector            z_score_XY;
   vector            z_score_YX;
   vector            Ave_TE_XY;
   vector            Ave_TE_YX;
  };

Die Eigenschaften der Ergebnisstruktur sind im Folgenden aufgeführt.

Struktur Eigenschaft
Beschreibung
TE_XY
Übertragung von Entropie von der exogenen zur
endogene Variable
TE_YX
Übertragung von Entropie von endogener zu
exogene Variable
z_score_XY
Bedeutung der Transferentropie
von der exogenen zur endogenen Variable
z_score_YX
Bedeutung der Transferentropie
von endogener zu exogener Variable
p_wert_XY
p-value Signifikanz der Übertragung
Entropie von exogener zu endogener Variable
p_wert_YX
p-value Signifikanz der Übertragung
Entropie von endogener zu exogener Variable
Ave_TE_XY
Durchschnittliche Transferentropie von exogener zu
endogene Variable
Ave_TE_YX
Durchschnittliche Transferentropie von endogener zu
exogene Variable

Der Aufruf von get_transfer_entropies() liefert einen Vektor der geschätzten Übertragungsentropien für das letzte Fenster im Datensatz, gemessen in beide Richtungen. Die Reihenfolge der Ergebnisse folgt der Spaltenreihenfolge der an die Klasse übergebenen Originaldaten. Der erste Entropiewert im Vektor entspricht also der Reihe in der ersten Spalte.


Beispiele

Die Funktionalität der Klasse wird durch die Durchführung von Tests an zufällig generierten Zeitreihen mit vorgegebenen Merkmalen geprüft. Die Reihen werden mit den unten aufgeführten Funktionen erzeugt, die beide in generate_time_series.mqh definiert sind.

//+------------------------------------------------------------------+
//|Generate a random walk time series under Geometric Brownian Motion|
//+------------------------------------------------------------------+
vector random_series(double initial_val, ulong steps, ulong len, double muu, double sgma)
  {
   vector out(len);

   out[0] = initial_val;

   int err=0;

   for(ulong i=1; i<len; i++)
     {
      out[i] = out[i-1]*(1.0+(muu*(double(steps)/double(len)))+(MathRandomNormal(muu,sgma,err)*sqrt(double(steps)/double(len))));
      if(err)
        {
         Print(__FUNCTION__, " MathRandonNormal() ", GetLastError());
         return vector::Zeros(1);
        }
     }

   return out;
  }

Die Funktion random_series() erzeugt eine für die geometrische Brownsche Bewegung charakteristische Zufallszeitreihe. Die Parameter sind:

  • initial_val : Der Anfangswert der Zeitreihe.
  • steps : Die Gesamtzahl der Schritte beim Random Walk.
  • len : Die Länge der zu erstellenden Zeitreihe.
  • muu : Der Driftterm (Mittelwert) des GBM.
  • sgma : Die Volatilität (Standardabweichung) des GBM.
//+-----------------------------------------------------------------------------------------------+
//|Generate two time series under Geometric Brownian Motion with S2 dependent in part on S1-lagged|
//+-----------------------------------------------------------------------------------------------+
matrix coupled_random_series(double init_1,double init_2,ulong steps, ulong len, double muu_1, double muu_2, double sgma_1, double sgma_2,
                            double alpha, double epsilon, ulong lag)
  {

   vector gbm1 = random_series(init_1,steps,len,muu_1,sgma_1);
   vector gbm2 = random_series(init_2,steps,len,muu_2,sgma_2);

   if(gbm1.Size()!=gbm2.Size())
     {
      return matrix::Zeros(1,1);
     }

   matrix out(gbm2.Size()-lag,2);

   for(ulong i = lag; i<gbm2.Size(); i++)
     {
      gbm2[i]=(1.0-alpha)*(epsilon*gbm2[i-lag] + (1.0-epsilon) * gbm2[i]) + (alpha) * gbm1[i-lag];
      out[i-lag][0] = gbm2[i];
      out[i-lag][1] = gbm1[i];
     }

   return out;
  }

Die Funktion coupled_random_series() erzeugt zwei gekoppelte Random-Walk-Zeitreihen, wobei die zweite Reihe (gbm2) teilweise von den verzögerten Werten der ersten Reihe (gbm1) abhängig ist. Die Funktion gibt eine zweispaltige Matrix zurück, wobei die abhängige Reihe in der ersten Spalte steht. Die Parameter der Funktion sind wie folgt:

  • init_1 : Der Anfangswert der ersten Zeitreihe.
  • init_2 : Der Anfangswert der zweiten Zeitreihe.
  • steps : Die Gesamtzahl der Schritte beim Random Walk.
  • len : Die Länge der zu erstellenden Zeitreihe.
  • muu_1 : Der Driftterm der ersten Reihe.
  • muu_2 : Der Driftterm der zweiten Reihe.
  • sgma_1 : Die Volatilität der ersten Serie.
  • sgma_2 : Die Volatilität der zweiten Reihe.
  • alpha : Ein Mischparameter für den Einfluss der unabhängigen Reihe auf die abhängige Reihe.
  • epsilon : Ein Parameter, der den Einfluss von verzögerten abhängigen Reihenwerten anpasst.
  • lag : Die Verzögerung für die Abhängigkeit der abhängigen Reihe von der unabhängigen Reihe.

Um die Fähigkeiten der CTransEntropy-Klasse zu demonstrieren, wurden zwei MetaTrader 5-Skripte vorbereitet. Beide Skripte veranschaulichen, wie die Klasse verwendet werden kann, um einen Datensatz zu analysieren und die Verzögerung der unabhängigen Variable (Zeitreihe) zu ermitteln, die die in der abhängigen Variable (Zeitreihe) beobachtete Abhängigkeit am besten charakterisiert. Die erste Methode beruht auf einer visuellen Inspektion, um den signifikantesten Wert der Richtungsentropie aus einer Reihe von Ergebnissen zu ermitteln, die durch die Analyse der Übertragungsentropie bei verschiedenen Verzögerungen gewonnen wurden. Diese Methode ist in dem Skript LagDetection.ex5 implementiert.

//+------------------------------------------------------------------+
//|                                                 LagDetection.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<transfer_entropy.mqh>
#include<generate_time_series.mqh>
//--- input parameters

input double   Init1=100.0;
input double   Init2=90.0;
input ulong    Steps=1;
input ulong    Len=500;
input double   Avg1=0;
input double   Avg2=0;
input double   Sigma1=1;
input double   Sigma2=1;
input double   Alph=0.5;
input double   Epsilon=0.3;
input ulong    Lag=3;
input bool     UseSeed = true;
input ulong    Bins=3;
input ENUM_TE_TYPE testtype=NONLINEAR_TE;
input ulong    NumLagsToTest = 10;
input int      PlotViewTimeInSecs = 20;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   if(UseSeed)
    {
     MathSrand(256);
    }
//---
   if(!NumLagsToTest)
     {
      Print(" Invalid input parameter value for \'NumLagsToTest\'. It must be > 0 ");
      return;
     }

   matrix series = coupled_random_series(Init1,Init2,Steps,Len,Avg1,Avg2,Sigma1,Sigma2,Alph,Epsilon,Lag);

   series = log(series);

   series = np::diff(series,1,false);
   
   matrix entropies(NumLagsToTest,2);

   for(ulong k = 0; k<NumLagsToTest; k++)
     {
      CTransEntropy ote;

      if(!ote.Initialize(series,0,1,k+1))
         return;

      if((testtype==NONLINEAR_TE && !ote.Calculate_NonLinear_TE(Bins)) ||
         (testtype==LINEAR_TE && !ote.Calculate_Linear_TE()))
         return;

      vector res = ote.get_transfer_entropies();

      entropies.Row(res,k);
     }

   Print(" entropies ", entropies);

   CGraphic* g = np::plotMatrix(entropies,"Transfer Entropies","Col 0,Col 1","Lag","TE");

   if(g==NULL)
      return;
   else
     {
      Sleep(int(MathAbs(PlotViewTimeInSecs))*1000);
      g.Destroy();
      delete g;
     }

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

Die ersten 11 für den Nutzer zugänglichen Eingabeparameter des Skripts steuern die Eigenschaften der erzeugten Reihe. Die letzten 4 Eingabeparameter konfigurieren verschiedene Aspekte der Analyse:

  • Bins : Legt die Anzahl der Bins für die Histogramm-Methode fest, die zur Schätzung der Wahrscheinlichkeitsdichte der Daten verwendet wird.
  • testtype : Ermöglicht die Auswahl einer linearen oder nichtlinearen Transferentropieanalyse.
  • NumLagsToTest : Legt die maximale Anzahl der Verzögerungen fest, mit denen die Tests durchgeführt werden, beginnend bei 1.
  • PlotViewTimeInSecs : Bestimmt die Zeitspanne, die das Diagramm sichtbar bleibt, bevor das Programm beendet wird.
  • UseSeed : Bei true wird der Seed für den Zufallszahlengenerator aktiviert, um die Reproduzierbarkeit der Testergebnisse zu gewährleisten.

Das Skript erzeugt zwei Zeitreihen mit einer vorgegebenen Abhängigkeit und schätzt die Transferentropie bei verschiedenen Verzögerungen. Beachten Sie, dass die Daten vor der Analyse differenziert wurden. In diesem Fall ist das wahrscheinlich nicht nötig, aber es ist eine gute Praxis. Die Ergebnisse (Transferentropien) werden dann in einem Diagramm dargestellt, in dem die Transferentropie auf der vertikalen Achse gegen die entsprechende Verzögerung auf der horizontalen Achse aufgetragen ist. Ein erfolgreiches Ergebnis des Tests sollte ein Diagramm mit einer deutlichen Spitze bei der Verzögerung ergeben, die für die Erzeugung der Zufallsreihe gewählt wurde.

Die Ausführung des Programms zeigt, dass der lineare Test erfolgreich die Verzögerungsabhängigkeit identifiziert hat, die zur Erstellung der Reihen verwendet wurde. Erinnern Sie sich, dass die abhängige Reihe in der ersten Spalte des zufällig generierten Datensatzes steht.

LagDetection Graph: Linearer Test

Eine erneute Durchführung des Tests mit der Option nichtlinearer Test führt zu ähnlichen Ergebnissen. In diesem Fall ist der Wert der Entropie deutlich geringer. Dies könnte auf die Einschränkungen bei der Verwendung der Histogramm-Methode zur Schätzung der Wahrscheinlichkeitsverteilung der Daten zurückzuführen sein. Es ist auch zu beachten, dass die Anzahl der gewählten Bins die geschätzte Transferentropie beeinflusst.  

LagDetection Ergebnis : Nichtlinearer Test

In der nächsten Demonstration testen wir die Signifikanz der Entropiewerte, die wir bei bestimmten Verzögerungen erhalten. Dies ist in dem Skript LagDetectionUsingSignificance.ex5 implementiert.

//+------------------------------------------------------------------+
//|                                LagDetectionUsingSignificance.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<transfer_entropy.mqh>
#include<generate_time_series.mqh>
//--- input parameters
input double   Init1=100.0;
input double   Init2=90.0;
input ulong    Steps=1;
input ulong    Len=500;
input double   Avg1=0;
input double   Avg2=0;
input double   Sigma1=1;
input double   Sigma2=1;
input double   Alph=0.5;
input double   Epsilon=0.3;
input ulong    Lag=3;
input bool     UseSeed = true;
input ulong    Bins=3;
input ENUM_TE_TYPE testtype=LINEAR_TE;
input ulong    LagToTest = 3;
input ulong    NumIterations = 100;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   if(UseSeed)
    {
     MathSrand(256);
    }
//---
   if(!LagToTest)
     {
      Print(" Invalid input parameter value for \'LagToTest\'. It must be > 0 ");
      return;
     }

   matrix series = coupled_random_series(Init1,Init2,Steps,Len,Avg1,Avg2,Sigma1,Sigma2,Alph,Epsilon,Lag);

   series = log(series);

   series = np::diff(series,1,false);

   matrix entropies(1,2);


   CTransEntropy ote;

   if(!ote.Initialize(series,0,1,LagToTest))
      return;

   if((testtype==NONLINEAR_TE && !ote.Calculate_NonLinear_TE(Bins,NumIterations)) ||
      (testtype==LINEAR_TE && !ote.Calculate_Linear_TE(NumIterations)))
      return;

   vector res = ote.get_transfer_entropies();

   entropies.Row(res,0);

   TEResult alres = ote.get_results();

   Print(" significance: ", " pvalue 1->0 ",alres.p_value_XY, " pvalue 0->1 ",alres.p_value_YX);
   Print(" zscore 1->0 ",alres.z_score_XY, " zscore 0->1 ",alres.z_score_YX);

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

Das Skript hat ähnliche, vom Nutzer einstellbare Eingabeparameter, mit Ausnahme der letzten beiden:

  • LagToTest: Legt die spezifische Verzögerung fest, mit der der Test durchgeführt wird.
  • NumIterations: Legt fest, wie oft die Daten für Signifikanztests gemischt werden.

Das Skript erzeugt ein Paar abhängiger Reihen und führt einen Test mit der gewählten Verzögerung durch. Die Transferentropie wird zusammen mit dem entsprechenden p-Wert und z-Score in die Registerkarte „Experten“ des Terminals geschrieben.

Skript-Parameter bei Lag 3

Beim ersten Durchlauf wird das Skript mit den Parametern LagToTest und Lag auf denselben Wert gesetzt. Die Ergebnisse werden unten angezeigt. Sie zeigen, dass die Reihe in der ersten Spalte von der Reihe in der zweiten Spalte der Matrix abhängig ist.

JS      0       21:33:43.464    LagDetectionUsingSignificance (Crash 1000 Index,M10)     significance:  pvalue 1->0 [0] pvalue 0->1 [0.66]
LE      0       21:33:43.464    LagDetectionUsingSignificance (Crash 1000 Index,M10)     zscore 1->0 [638.8518379295961] zscore 0->1 [-0.5746565128024472]

Im zweiten Durchlauf ändern wir nur den Wert des Parameters LagToTest und vergleichen diese Ergebnisse mit denen des vorherigen Durchlaufs.

Skript-Parameter bei Lag 5


Beachten Sie die Unterschiede bei den p-Werten und z-Scores. In diesem Fall sind sowohl die p-Werte als auch die z-Scores unbedeutend.

RQ      0       21:33:55.147    LagDetectionUsingSignificance (Crash 1000 Index,M10)     significance:  pvalue 1->0 [0.37] pvalue 0->1 [0.85]
GS      0       21:33:55.147    LagDetectionUsingSignificance (Crash 1000 Index,M10)     zscore 1->0 [-0.2224969673139822] zscore 0->1 [-0.6582062358345131]

Die Ergebnisse der Tests zeigen zwar, dass die Klasse CTransEntropy gut abschneidet, aber es gibt eine erhebliche Einschränkung bei der Durchführung von Analysen mit größeren Verzögerungen, insbesondere wenn die Option für mehrere Verzögerungsterme aktiviert ist (maxLagOnly ist falsch). Besonders problematisch ist dies bei dem nichtlinearen Test. Dies ergibt sich aus der Verwendung der Histogramm-Methode zur Schätzung der Verteilung der Daten. Die Verwendung der Histogramm-Methode zur Schätzung von Wahrscheinlichkeitsdichten hat erhebliche Nachteile. Die Wahl der Bin-Breite (oder Bin-Anzahl) wirkt sich erheblich auf das Aussehen und die Genauigkeit des Histogramms aus. Eine zu geringe Bin-Breite kann zu einem verrauschten und fragmentierten Histogramm führen, während eine zu große Bin-Breite wichtige Details verdecken und Merkmale überdecken kann. Das größte Problem ist die Tatsache, dass Histogramme in erster Linie für eindimensionale Daten geeignet sind. Bei höherdimensionalen Daten wächst die Anzahl der Bins exponentiell. Wenn zahlreiche Verzögerungen zu berücksichtigen sind, können die Anforderungen an die verfügbaren Rechenressourcen erheblich strapaziert werden. Es wird daher empfohlen, die maximale Anzahl der Lags gering zu halten, wenn die Analyse unter Berücksichtigung mehrerer Lags mit Hilfe der verallgemeinerten Transferentropie durchgeführt wird.  


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass die Klasse CTransEntropy die Analyse der Transferentropie sowohl in linearen als auch in nichtlinearen Zusammenhängen ermöglicht. Durch praktische Demonstrationen haben wir gezeigt, dass es in der Lage ist, den Einfluss einer Zeitreihe auf eine andere zu erkennen und zu quantifizieren, wobei die Ergebnisse durch visuelle Inspektion und Signifikanztests bestätigt wurden. Die Klasse behandelt effektiv verschiedene Szenarien und bietet wertvolle Einblicke in kausale Beziehungen innerhalb von Zeitreihenanwendungen. Allerdings sollten sich die Nutzer der rechnerischen Herausforderungen bewusst sein, die mit der Analyse mehrerer Verzögerungen verbunden sind, insbesondere bei der Anwendung nichtlinearer Methoden. Um eine effiziente Leistung und genaue Ergebnisse zu gewährleisten, ist es ratsam, die Anzahl der berücksichtigten Verzögerungen zu begrenzen. Insgesamt ist die Klasse CTransEntropy ein praktisches Werkzeug, um komplexe Abhängigkeiten aufzudecken und das Verständnis für dynamische Systeme zu verbessern.

Datei
Beschreibung
Mql5\include\generate_time_series.mqh
enthält Funktionen zur Erzeugung zufälliger Zeitreihen.
Mql5\include\ np.mqh
eine Sammlung von Vektor- und Matrixnutzenfunktionen.
Mql5\include\ OLS.mqh
enthält die Definition der OLS-Klasse, die die Regression der gewöhnlichen kleinsten Quadrate implementiert.
Mql5\include\ TestUtilities.mqh
bietet eine Sammlung von Tools zur Vorbereitung von Datensätzen für die OLS-Auswertung.
Mql5\include\ transfer_entropy
enthält die Definition der Klasse CTransEntropy, die die Transferentropieanalyse implementiert.
Mql5\scripts\LagDetection.mq5
ein Skript, das die Funktionalität der Klasse CTransEntropy demonstriert.
Mql5\scripts\LagDetectionUsingSignificance.mq5
ein zweites Skript, das einen anderen Ansatz für die Interpretation des Ergebnisses der Verwendung von CTransEntropy veranschaulicht.


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

Beigefügte Dateien |
np.mqh (51.13 KB)
OLS.mqh (13.36 KB)
TestUtilities.mqh (4.36 KB)
LagDetection.mq5 (2.43 KB)
Mql5.zip (18.26 KB)
Erstellen eines Dashboards in MQL5 für den RSI-Indikator von mehreren Symbolen und Zeitrahmen Erstellen eines Dashboards in MQL5 für den RSI-Indikator von mehreren Symbolen und Zeitrahmen
In diesem Artikel entwickeln wir ein dynamisches RSI-Indikator-Dashboard in MQL5, das Händlern Echtzeit-RSI-Werte für verschiedene Symbole und Zeitrahmen anzeigt. Das Dashboard bietet interaktive Schaltflächen, Echtzeit-Updates und farbkodierte Indikatoren, die Händlern helfen, fundierte Entscheidungen zu treffen.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 28): GANs überarbeitet mit einer Anleitung zu Lernraten MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 28): GANs überarbeitet mit einer Anleitung zu Lernraten
Die Lernrate ist eine Schrittgröße in Richtung eines Trainingsziels in den Trainingsprozessen vieler maschineller Lernalgorithmen. Wir untersuchen die Auswirkungen, die die vielen Zeitpläne und Formate auf die Leistung eines Generative Adversarial Network haben können, eine Art neuronales Netz, das wir in einem früheren Artikel untersucht haben.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 29): Fortsetzung zu Lernraten mit MLPs MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 29): Fortsetzung zu Lernraten mit MLPs
Zum Abschluss unserer Betrachtung der Empfindlichkeit der Lernrate für die Leistung von Expert Advisors untersuchen wir in erster Linie die adaptiven Lernraten. Diese Lernraten sollen für jeden Parameter in einer Schicht während des Trainingsprozesses angepasst werden, und so bewerten wir die potenziellen Vorteile gegenüber der erwarteten Leistungsgebühr.
Vom Neuling zum Experten: Die wesentliche Reise durch den MQL5-Handel Vom Neuling zum Experten: Die wesentliche Reise durch den MQL5-Handel
Entfalten Sie Ihr Potenzial! Sie sind von Möglichkeiten umgeben. Entdecken Sie die 3 wichtigsten Geheimnisse, um Ihre MQL5-Reise in Gang zu bringen oder auf die nächste Stufe zu heben. Lassen Sie uns in die Diskussion über Tipps und Tricks für Anfänger und Profis gleichermaßen eintauchen.