English Deutsch 日本語
preview
Análisis causal de series temporales mediante entropía de transferencia

Análisis causal de series temporales mediante entropía de transferencia

MetaTrader 5Ejemplos | 16 enero 2025, 09:11
134 0
Francis Dube
Francis Dube

Introducción

La entropía de transferencia es una herramienta estadística que cuantifica la cantidad de información transferida de una serie temporal a otra, proporcionando información sobre la naturaleza y el comportamiento de una variable objetivo. En este artículo profundizamos en el concepto de causalidad estadística, calculada en términos de entropía de transferencia. Exploramos cómo este método puede revelar la dirección de la influencia causal entre varios procesos. Además, proporcionamos una descripción detallada de una implementación de MQL5 para medir la entropía de transferencia, demostrando cómo esta técnica se puede aplicar prácticamente para analizar series de tiempo potencialmente acopladas. Aprovechando la entropía de transferencia, pretendemos identificar variables que puedan mejorar las tareas de predicción.


Causalidad

Los datos empíricos pueden ser engañosos. El hecho de que dos variables parezcan moverse en tándem no significa que una cause la otra, por eso suena cierto el dicho "la correlación no es causalidad". La correlación simplemente mide qué tan conectadas están dos variables, no por qué están conectadas. Por ejemplo, imaginemos una fuerte correlación entre las ventas de helado y el precio de una acción durante el verano. ¡Esto no significa que comprar helado haga subir las acciones! Un culpable más probable es un factor oculto, como la temporada misma, que afecta a ambas variables independientemente. De manera similar, podría existir un vínculo entre las acciones de una empresa y los precios del oro, pero la causa real podría ser algo completamente distinto, como el sentimiento general del mercado o la inflación que influyen en ambos precios. Estos ejemplos resaltan que los datos correlacionados pueden ser engañosos. Muestran una conexión, pero no la razón detrás de ella. Para comprender verdaderamente si una cosa causa otra, necesitamos herramientas más avanzadas.

Péndulo

El concepto de causalidad, la noción de que un acontecimiento provoca otro, es fundamental para la exploración científica. Sin embargo, definir con precisión la causalidad presenta un desafío multifacético con profundas consideraciones filosóficas, físicas y estadísticas. Lo ideal sería que una causa produjera invariablemente un efecto singular. Sin embargo, aislar un único factor causal de la red a menudo compleja de influencias que inciden en un resultado puede resultar difícil. Por ejemplo, un aumento en el volumen de operaciones podría correlacionarse con un aumento en el precio de las acciones, pero otros factores, como el sentimiento del mercado y la publicación de datos económicos, también podrían desempeñar un papel importante. En tales escenarios, los investigadores emplean técnicas estadísticas para inferir relaciones causales.

La naturaleza de la causalidad, ya sea determinista (resultado garantizado) o probabilística (que influye en la probabilidad del evento posterior), depende del proceso subyacente. En los sistemas deterministas, el primer evento conduce demostrablemente al segundo, como se observa en la caída predecible de un objeto. Por el contrario, en los sistemas probabilísticos, el foco se desplaza a si el primer evento mejora nuestra capacidad de predecir la ocurrencia del segundo. Por ejemplo, aunque las lluvias recientes pueden estar asociadas con la posterior floración de flores, otros factores ambientales también podrían contribuir. En tales casos, la pregunta es si el conocimiento del primer evento mejora nuestra capacidad de predecir el segundo.

El economista Clive Granger, basándose en el trabajo de Norbert Wiener, desarrolló el concepto de relaciones causales, postulando que una primera señal causa una segunda señal si los valores futuros de la segunda señal pueden explicarse mejor utilizando información pasada tanto de la primera como de la segunda señal, en lugar de utilizar únicamente valores retardados de la segunda señal. Granger basó su definición de causalidad en dos principios. En primer lugar, un efecto no puede manifestarse antes que su causa. En segundo lugar, una causa contendrá información única transferida al efecto. Estos principios sugieren que, para cuantificar la causalidad, necesitamos comprender las propiedades temporales de las variables involucradas, así como alguna medida de su contenido de información. Esto hace que las series temporales sean adecuadas para el análisis causal.

Causa y efecto

Debido a la naturaleza de los datos de series de tiempo, podemos analizar cómo la información de una serie en un momento determinado afecta la previsibilidad de otra serie en un momento posterior. Granger define la causalidad como una reducción de la incertidumbre. Si conocer los valores pasados de la serie X mejora nuestra predicción del valor futuro de la serie Y en comparación con usar solo los valores pasados de la propia Y, entonces se dice que X es predictivo de Y. Basándose en esta idea, Granger desarrolló pruebas de causalidad utilizando series de tiempo rezagadas y modelos autorregresivos. Propuso que no existe una relación causal entre X e Y a menos que incluir valores pasados de X mejore significativamente la predicción de los valores futuros de Y. Esta mejora generalmente se mide mediante una reducción en el error de predicción, como un mejor ajuste en un modelo de regresión. La causalidad de Granger nos permite detectar estadísticamente relaciones entre series de tiempo. Sin embargo, es importante tener en cuenta sus limitaciones. La causalidad de Granger sólo identifica una relación direccional, no necesariamente un mecanismo causal definitivo. Podría haber otras causas en juego además de aquellas sobre las que tenemos datos. Además, dado que la causalidad de Granger se basa esencialmente en el marco de autorregresión, es más eficaz para descubrir relaciones causales lineales. La causalidad no lineal requiere un enfoque diferente.

Matemáticamente, la causalidad de Granger puede expresarse considerando dos series temporales, X e Y. Los valores retardados de cada una se denotan por X(t-k) e Y(t-k), representando un retardo en k. El retraso máximo considerado se denota como p. Aplicando el modelo de autorregresión, el valor futuro de Y se regresiona sobre sus propios valores pasados.

Fórmula de autorregresión

Esta expresión considera el valor futuro de Y en términos de sus valores pasados únicamente. Al introducir X en el modelo, los valores futuros se expresan en términos de valores pasados de X e Y.

Fórmula de autorregresión vectorial

Si la inclusión de los valores pasados de X mejora significativamente la predicción de Y en comparación con el modelo que utiliza solo los valores pasados de Y, entonces se dice que X causa Y en el sentido de Granger. Esto normalmente se evalúa probando la hipótesis nula de que los coeficientes son conjuntamente cero. Si se rechaza esta hipótesis nula, indica que X proporciona información predictiva significativa acerca de Y más allá de lo que contienen los valores pasados de Y únicamente. Para comprobar si X causa en sentido de Granger a Y, comparamos los dos modelos bajo la prueba de hipótesis:

  • Hipótesis nula: X no causa Granger a Y.
  • Hipótesis alternativa: X causa Granger a Y.

Se utiliza una prueba F para comparar el ajuste del modelo restringido (sin X) y el modelo sin restricciones (con X) examinando los residuos de los respectivos modelos. La suma restringida de los residuos al cuadrado son los residuos del modelo sin X, y la suma no restringida de los residuos al cuadrado proviene del modelo con X. La estadística F se calcula como:

Fórmula de la estadística F

Donde n es el número de observaciones. La estadística F calculada se compara con el valor crítico de la distribución F con p y n − 2p − 1 grados de libertad. Si la estadística F es mayor que el valor crítico, rechazamos la hipótesis nula y concluimos que X causa Y en el sentido de Granger. Alternativamente, la estadística F también se puede calcular utilizando una prueba de análisis de varianza (ANOVA) unidireccional. Cuya fórmula se da a continuación.

Causalidad de Granger basada en ANOVA

Entropía de transferencia

En los inicios de la teoría de la información, los científicos utilizaban la información mutua para comprender cómo interactuaban los procesos acoplados. Este concepto, basado en la entropía de Claude Shannon, nos dice si la información de una serie temporal se superpone con otra. En términos más simples, revela si podemos codificar ambas series juntas usando menos información que codificándolas por separado. Debido a esto, a la información mutua a veces se le llama redundancia. Un proceso comparte información con otro, lo que permite que el segundo proceso se describa de manera eficiente reutilizando información ya capturada del primer proceso.

Formalmente, la información mutua entre dos procesos estocásticos, X(t) e Y(t), se establece cuando la suma de sus entropías marginales excede la entropía conjunta del sistema combinado. Esta relación matemática refleja la reducción de la incertidumbre sobre el sistema combinado en comparación con los procesos individuales. En otras palabras, captura el grado en que la información sobre un proceso puede utilizarse para reducir la entropía inherente asociada con el otro. Dado que la entropía está determinada únicamente por la distribución de probabilidad subyacente, cualquier distribución de este tipo puede caracterizarse mediante un valor de entropía asociado. Este valor cuantifica el nivel de imprevisto asociado con un resultado particular, dada la distribución de probabilidad conocida.

Este concepto se vuelve particularmente relevante en el contexto de la causalidad de Granger. Al investigar posibles relaciones causales entre series de tiempo, el objetivo es reducir la incertidumbre asociada con un proceso objetivo incorporando información de un proceso fuente potencial. Si la inclusión de una serie temporal secundaria reduce de manera demostrable la entropía de la distribución del proceso objetivo, sugiere la presencia de una influencia causal estadística de la serie fuente a la serie objetivo. Esta reducción se denomina transferencia de entropía.


La entropía de transferencia (TE) se basa en el concepto de divergencia de Kullback-Leibler para medir la dirección de la transferencia de información entre dos series temporales. En concreto, la TE se basa en la idea de información mutua condicional y puede expresarse mediante la distancia Kullback-Leibler (KL), también conocida como divergencia de Kullback-Leibler o entropía relativa. La divergencia KL mide la diferencia entre dos distribuciones de probabilidad. En TE, la distancia KL mide la diferencia entre la distribución de probabilidad conjunta del estado actual de Y y los estados pasados de X e Y, y el producto de las distribuciones marginales de estos estados. Matemáticamente, la transferencia de información de una serie temporal X a una serie temporal Y se puede expresar como:

Fórmula de transferencia de entropía



Donde y(t+1)​ es el estado futuro de Y, y(t)​ es el estado pasado de Y, y x(t) es el estado pasado de X. Esta formulación resalta que la entropía de transferencia mide cuánto cambia la distribución de probabilidad de y(t+1)​ cuando se considera la información de x(t) además de y(t)​.

En 2009, Lionel Barnett, Adam Barrett y Anil Seth fueron coautores del artículo «Granger Causality and Transfer Entropy Are Equivalent for Gaussian Variables» (La causalidad de Granger y la entropía de transferencia son equivalentes para las variables gaussianas), en el que demostraban que cuando las series temporales siguen una distribución gaussiana, la entropía de transferencia es equivalente a la mitad del estadístico F para la causalidad de Granger.

Causalidad en términos de la fórmula de entropía de transferencia

Este resultado proporciona la definición de entropía de transferencia lineal que implementaremos en el código más adelante. Para tener en cuenta la causalidad no lineal, ampliamos el concepto de reducción de incertidumbre siguiendo el trabajo de Thomas Schreiber, que trata las series de tiempo como un proceso de Markov con distribuciones de probabilidad de transición distintas.

El enfoque de Schreiber para modelar la reducción de la incertidumbre aprovecha la teoría de la información al tratar las series de tiempo X(t) e Y(t) como procesos de Markov con probabilidades de transición conocidas p(x) y q(x). A diferencia del modelo autorregresivo de Granger, que se basa en modelos lineales, este enfoque utiliza información mutua condicional para describir la transferencia de información. Dado que la información mutua se deriva de la diferencia de entropías, la información mutua condicional se obtiene condicionando cada término de entropía a información adicional. Luego se calcula la entropía de transferencia sustituyendo las variables rezagadas en la ecuación de información mutua condicional, lo que nos permite analizar la transferencia de información de X(t) a Y(t) en un rezago específico k utilizando la entropía mutua condicional.

Entropía condicional conjunta

Entropía condicional independiente

Computacionalmente, este método es atractivo porque la entropía conjunta solo requiere una distribución de probabilidad. La entropía de transferencia para un solo rezago k se puede expresar como cuatro términos de entropía conjuntos separados, que se calculan fácilmente con una distribución de probabilidad precisa a partir de los datos. La ventaja de esta fórmula es su capacidad para manejar dimensiones más retrasadas. Sin embargo, cada retraso adicional aumenta la dimensionalidad del espacio de estados en dos, lo que afecta significativamente la capacidad de cuantificar con precisión la entropía de transferencia debido al crecimiento exponencial de los problemas de datos finitos asociados con la estimación de densidades de probabilidad.

Términos de entropía no lineal

Una fortaleza clave de este enfoque reside en su naturaleza no paramétrica. A diferencia de otros métodos, no hace suposiciones sobre la distribución de datos subyacente más allá de la estacionariedad, lo que permite su aplicación sin conocimiento previo de los procesos de generación de datos. Sin embargo, esta ventaja tiene una salvedad: los resultados dependen en gran medida de una estimación precisa de la distribución subyacente. La transferencia de entropía requiere aproximar la distribución de probabilidad real de los procesos estocásticos involucrados, utilizando datos limitados para calcular los cuatro términos de entropía. La precisión de esta estimación afecta significativamente la confiabilidad de los hallazgos de entropía de transferencia. Teniendo esto en mente, hay que considerar la posibilidad de que los valores de entropía calculados sean espurios. Por lo tanto, necesitamos alguna forma de determinar la solidez de los resultados.

Nuestra insistencia en emplear un enfoque no paramétrico para estimar la entropía de transferencia conlleva el desafío considerable de garantizar que los resultados transmitan cierta verdad y no basura. Por lo tanto, nos conviene considerar un enfoque más informativo para interpretar la entropía de transferencia, que implica evaluar la significancia estadística del valor estimado. Las pruebas de significancia comunes implican mezclar los datos de series de tiempo un número predefinido de veces. Luego se calcula la entropía de transferencia para cada versión mezclada. El valor p se calcula posteriormente como la proporción de datos mezclados con una entropía de transferencia inferior al valor original.

Otro enfoque requiere calcular el número de desviaciones estándar a las que se encuentra un resultado respecto de la media de los datos mezclados. Dado que la mezcla altera la estructura temporal, se espera que la media de los valores de entropía de transferencia mezclados sea cercana a cero. La dispersión de los datos en torno a esta media refleja la importancia del resultado original. El valor calculado se llama puntuación z. Los puntajes Z generalmente requieren menos cambios en comparación con los valores p, lo que los hace computacionalmente más eficientes. 

En el caso del valor p, el objetivo es obtener una probabilidad lo más cercana posible a cero. Mientras que una puntuación z indicativa de significación estadística debe ser superior a 3,0.


Implementación en MQL5

El código que implementa las herramientas para cuantificar la entropía de transferencia y determinar su importancia está incluido en transfer_entropy.mqh. El archivo contiene la definición de la clase, CTransEntropy, junto con otras clases y funciones auxiliares. Esta clase ofrece un marco para el análisis estadístico de datos de series de tiempo, específicamente orientado a evaluar las relaciones causales entre variables. Expone dos métodos distintos para cuantificar la causalidad de Granger lineal (entropía de transferencia lineal) y la entropía de transferencia no lineal. Que se calcula en ambas direcciones, proporcionando una imagen más completa del flujo de información entre las variables.

Para abordar la posible no estacionariedad de los datos, la clase incorpora un procedimiento de ventanas. Los usuarios pueden definir el tamaño y el paso de la ventana, lo que permite el análisis de los datos en segmentos más pequeños y superpuestos. Este enfoque produce resultados específicos para cada ventana, lo que facilita la identificación de variaciones temporales en la fuerza de la causalidad. Además, mitiga los desafíos asociados con el análisis de datos no estacionarios. La clase también proporciona un mecanismo de prueba de significación integrado. Los usuarios pueden especificar el número de mezclas de datos a realizar conservando las distribuciones marginales. A partir de estos conjuntos de datos mezclados, la clase calcula valores p y puntuaciones z para la entropía de transferencia en cada dirección. Estos valores estadísticos proporcionan información esencial sobre la probabilidad de que las relaciones causales observadas o la transferencia de información se deban al azar, lo que mejora la solidez del análisis.

Se crea una instancia de la clase utilizando el constructor predeterminado sin parámetros.

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

     }

Luego, los usuarios deben llamar al método Initialize(), que inicializa el objeto con un conjunto de datos determinado y configura varios parámetros para el análisis.

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;
     }

El primer parámetro requerido es una matriz con al menos dos columnas, donde las series de tiempo a analizar deben colocarse en las columnas de la matriz de entrada. Si se trabaja con datos no estacionarios, se recomienda diferenciar los datos de antemano. El segundo y tercer parámetro son los índices de columna de la matriz de datos de entrada, que indican las series de tiempo endógenas (dependientes) y las series de tiempo exógenas (independientes), respectivamente.

El cuarto parámetro, lag, define el parámetro de rezago considerado en el análisis. El siguiente parámetro booleano, maxLagOnly, determina si lag define un solo término (si es verdadero) o todos los valores rezagados hasta lag inclusive (si es falso). El segundo parámetro antes del último, winsize, denota la longitud de la ventana. Si se establece en 0, no se aplicará ninguna ventana a los datos. Por último, winstride establece opcionalmente el paso de la ventana para operaciones de ventanas, definiendo el paso entre ventanas consecutivas a medida que pasan sobre los datos de la serie temporal.

El método comienza asegurando que los índices endógenos y exógenos no sean los mismos. Si es así, imprime un mensaje de error y devuelve falso. La matriz interna m_dataset se redimensiona para almacenar el conjunto de datos bivariados que se analizará. Luego copia las columnas especificadas por endog_index y exog_index de la matriz de entrada en la primera y segunda columna de m_dataset, respectivamente. Si se solicita la creación de ventanas, se utiliza la clase auxiliar CDataWindows para crear ventanas en la matriz m_dataset. Una vez hecho esto, el método establece variables internas con los parámetros proporcionados para su uso posterior.

//+------------------------------------------------------------------+
//|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;
     }
  };

Si el método Initialize() se completa correctamente, los usuarios pueden llamar a Calculate_Linear_TE() o Calculate_NonLinear_TE() para probar la entropía de transferencia lineal y no lineal, respectivamente. Ambos métodos devuelven un valor booleano al finalizar. El método Calculate_Linear_TE() puede tomar un solo parámetro opcional, n_shuffles. Si n_shuffles es cero (valor predeterminado), no se realizan pruebas de significancia.

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;
     }

El método calcula la entropía de transferencia lineal utilizando el método de Granger. Esto se implementa en el método privado, linear_transfer(). Los dos últimos parámetros de esta rutina identifican la variable dependiente e independiente (columna) en la matriz de entrada. Al llamar al método dos veces con los índices de columna cambiados, podemos obtener la entropía de transferencia en ambas direcciones.

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;

     }

El método Calculate_NonLinear_TE() toma un parámetro adicional, numBins, junto con n_shuffles. Este parámetro define el número de contenedores utilizados para estimar la densidad de probabilidad de las variables. 

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;


     }

El método del histograma se utiliza para estimar la densidad de probabilidad. Se eligió porque es el más sencillo de implementar. La responsabilidad de calcular la versión generalizada de la entropía de transferencia se delega a los métodos privados, nonlinear_entropy() y get_entropy(). 

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();

     }

Los cuatro valores de los componentes utilizados para calcular las entropías condicionales conjuntas e independientes se combinan en nonlinear_transfer() para obtener la estimación final.

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;

     }

Se puede acceder a los resultados completos de la prueba utilizando el método get_results(), que devuelve una estructura de vectores. Cada miembro de esta estructura se refiere a un aspecto diferente de los resultados, y la longitud de cada vector depende de los parámetros establecidos por el método Initialize() y del tipo de análisis de entropía de transferencia realizado.

//+------------------------------------------------------------------+
//| 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;
  };

Las propiedades de la estructura de resultados se enumeran a continuación.

Propiedad de estructura
Descripción
TE_XY
Transferir entropía de exógeno a
variable endógena
TE_YX
Transferir entropía de endógena a
variable exógena
z_score_XY
Importancia de la entropía de transferencia
De variable exógena a variable endógena
z_score_YX
Importancia de la entropía de transferencia
De variable endógena a variable exógena
p_value_XY
Significación del valor p de la transferencia
Entropía de variable exógena a variable endógena
p_value_YX
Significación del valor p de la transferencia
Entropía de variable endógena a variable exógena
Ave_TE_XY
Entropía de transferencia promedio de exógeno a
variable endógena
Ave_TE_YX
Entropía de transferencia promedio de endógena a
variable exógena

Al llamar a get_transfer_entropies() se devuelve un vector de las entropías de transferencia estimadas para la última ventana del conjunto de datos, medidas en ambas direcciones. El orden de los resultados sigue el orden de las columnas de los datos originales pasados a la clase. Entonces, el primer valor de entropía en el vector corresponde a la serie en la primera columna.


Ejemplos

La funcionalidad de la clase se probará ejecutando pruebas en series generadas aleatoriamente con características predeterminadas. Las series se generan utilizando las funciones enumeradas a continuación, ambas definidas en generate_time_series.mqh.

//+------------------------------------------------------------------+
//|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;
  }

La función random_series() genera una serie temporal de caminata aleatoria característica del movimiento browniano geométrico. Sus parámetros son:

  • initial_val: El valor inicial de la serie temporal.
  • steps: El número total de pasos en el paseo aleatorio.
  • len: La longitud de la serie temporal que se generará.
  • muu: El término de deriva (media) del GBM.
  • sgma: La volatilidad (desviación estándar) del 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;
  }

La función coupled_random_series() genera dos series de tiempo de caminata aleatoria acopladas, donde la segunda serie (gbm2) depende parcialmente de los valores rezagados de la primera serie (gbm1). La función devuelve una matriz con dos columnas, con la serie dependiente colocada en la primera columna. Los parámetros de la función son los siguientes:

  • init_1: El valor inicial de la primera serie de tiempo.
  • init_2: El valor inicial de la segunda serie de tiempo.
  • steps: El número total de pasos en el paseo aleatorio.
  • len: La longitud de la serie temporal que se generará.
  • muu_1: El término de deriva de la primera serie.
  • muu_2: El término de deriva de la segunda serie.
  • sgma_1 : La volatilidad de la primera serie.
  • sgma_2: La volatilidad de la segunda serie.
  • alpha: Un parámetro de mezcla para la influencia de la serie independiente en la serie dependiente.
  • epsilon: Un parámetro que ajusta la influencia de los valores de las series dependientes rezagadas.
  • lag: El desfase entre la dependencia de la serie dependiente y la serie independiente.

Para demostrar las capacidades de la clase CTransEntropy, se prepararon dos scripts de MetaTrader 5. Ambos scripts ilustran cómo puede utilizarse la clase para analizar un conjunto de datos y detectar el retardo de la variable independiente (serie temporal) que mejor caracteriza la dependencia observada en la variable dependiente (serie temporal). El primer método se basa en la inspección visual para determinar el valor de entropía direccional más significativo a partir de un conjunto de resultados obtenidos mediante el análisis de la entropía de transferencia en diferentes rezagos. Este método se implementa en el script LagDetection.ex5.

//+------------------------------------------------------------------+
//|                                                 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;
  }
//+------------------------------------------------------------------+

Los primeros 11 parámetros de entrada accesibles para el usuario del script controlan las propiedades de la serie generada. Los últimos 4 parámetros de entrada configuran varios aspectos del análisis:

  • Bins: establece el número de bins para el método de histograma utilizado para estimar la densidad de probabilidad de los datos.
  • testtype: permite la selección de análisis de entropía de transferencia lineal o no lineal.
  • NumLagsToTest: establece el número máximo de retrasos con los que se realizarán las pruebas, comenzando en 1.
  • PlotViewTimeInSecs: determina la cantidad de tiempo que el gráfico permanecerá visible antes de que el programa salga.
  • UseSeed: si es verdadero, habilita la semilla para el generador de números aleatorios para garantizar la reproducibilidad de los resultados de la prueba.

El script genera dos series de tiempo con una dependencia predeterminada y estima la entropía de transferencia en diferentes rezagos. Tenga en cuenta que los datos fueron diferenciados antes de ser analizados. Probablemente no sea necesario en este caso, pero es una buena práctica. Los resultados (entropías de transferencia) se visualizan luego en un gráfico, donde la entropía de transferencia se representa en el eje vertical contra el rezago correspondiente en el eje horizontal. Un resultado exitoso de la prueba debería producir un gráfico con un pico claro en el rezago que se eligió para generar la serie aleatoria.

La ejecución del programa muestra que la prueba lineal identificó con éxito la dependencia del rezago utilizada para generar la serie. Recuerde que la serie dependiente está en la primera columna del conjunto de datos generado aleatoriamente.

Gráfico de detección de retraso: prueba lineal

Realizar la prueba nuevamente con la opción de prueba no lineal arroja resultados similares. En este caso, la magnitud del valor de la entropía es notablemente menor. Esto podría deberse a las limitaciones del uso del método de histograma para estimar la distribución de probabilidad de los datos. También debe tenerse en cuenta que la cantidad de contenedores seleccionados afectará la entropía de transferencia estimada.  

Resultado de detección de retraso: prueba no lineal

En la siguiente demostración, probamos la importancia de los valores de entropía obtenidos en rezagos específicos. Esto se implementa en el script LagDetectionUsingSignificance.ex5.

//+------------------------------------------------------------------+
//|                                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;
  }
//+------------------------------------------------------------------+

El script tiene parámetros de entrada ajustables por el usuario similares, excepto los dos últimos:

  • LagToTest: establece el retraso específico en el que se realizará la prueba.
  • NumIterations: define la cantidad de veces que se mezclarán los datos para realizar pruebas de significancia.

El script genera un par de series dependientes y realiza una prueba en el desfase elegido. La entropía de transferencia, junto con el valor p y la puntuación z correspondientes, se escriben en la pestaña Expertos del terminal.

Parámetros del script en el retraso 3

Para la primera ejecución, el script se ejecuta con los parámetros LagToTest y Lag establecidos en el mismo valor. Los resultados se muestran a continuación. Muestran que la serie de la primera columna depende de la serie de la segunda columna de la matriz.

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]

En la segunda ejecución, modificamos solo el valor del parámetro LagToTest y comparamos estos resultados con los de la ejecución anterior.

Parámetros del script en el retraso 5


Observe las diferencias en los valores p y las puntuaciones z. En este caso, tanto los valores p como las puntuaciones z son insignificantes.

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]

Si bien los resultados de las pruebas indican que la clase CTransEntropy funciona bien, existe una limitación significativa al realizar análisis con rezagos mayores, especialmente cuando la opción para múltiples términos de rezago está habilitada (maxLagOnly es falso). Esto es particularmente problemático con la prueba no lineal. Esto se debe al uso del método del histograma para estimar la distribución de los datos. El uso del método de histograma para estimar densidades de probabilidad tiene desventajas notables. La elección del ancho del bin (o número de bin) afecta significativamente la apariencia y la precisión del histograma. Un ancho de bin demasiado pequeño puede generar un histograma ruidoso y fragmentado, mientras que un ancho de bin demasiado grande puede oscurecer detalles importantes y suavizar características. El mayor problema se relaciona con el hecho de que los histogramas son principalmente efectivos para datos unidimensionales. Para datos de mayor dimensión, el número de contenedores crece exponencialmente. Si hay que tener en cuenta numerosos retrasos, las demandas sobre los recursos computacionales disponibles pueden verse aumentadas significativamente. Por lo tanto, se recomienda mantener pequeño el número máximo de rezagos cuando se realiza el análisis considerando múltiples rezagos utilizando entropía de transferencia generalizada.  


Conclusión

En conclusión, la clase CTransEntropy permite el análisis de la entropía de transferencia en contextos lineales y no lineales. A través de demostraciones prácticas, hemos demostrado su capacidad para detectar y cuantificar la influencia de una serie temporal sobre otra, con resultados validados mediante inspección visual y pruebas de significancia. La clase maneja eficazmente varios escenarios, ofreciendo información valiosa sobre las relaciones causales dentro de las aplicaciones de series de tiempo. Sin embargo, los usuarios deben tener en cuenta los desafíos computacionales asociados con el análisis de múltiples rezagos, particularmente cuando se aplican métodos no lineales. Para garantizar un rendimiento eficiente y resultados precisos, es aconsejable limitar el número de rezagos considerados. En general, la clase CTransEntropy es una herramienta útil para descubrir dependencias complejas y mejorar la comprensión de los sistemas dinámicos.

Archivo
Descripción
Mql5\include\generate_time_series.mqh
Contiene funciones para generar series de tiempo aleatorias.
Mql5\include\ np.mqh
Colección de funciones de utilidad matriciales y vectoriales.
Mql5\include\ OLS.mqh
Contiene la definición de la clase MCO que implementa la regresión de mínimos cuadrados ordinarios.
Mql5\include\ TestUtilities.mqh
Proporciona una colección de herramientas utilizadas para preparar conjuntos de datos para la evaluación de OLS.
Mql5\include\ transfer_entropy
Contiene la definición de la clase CTransEntropy que implementa el análisis de entropía de transferencia.
Mql5\scripts\LagDetection.mq5
Script que demuestra la funcionalidad de la clase CTransEntropy.
Mql5\scripts\LagDetectionUsingSignificance.mq5
Un segundo script que ilustra un enfoque diferente para interpretar el resultado del uso de CTransEntropy.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15393

Archivos adjuntos |
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)
Kit de herramientas de negociación MQL5 (Parte 2): Ampliación e implantación de la biblioteca EX5 de gestión de posiciones Kit de herramientas de negociación MQL5 (Parte 2): Ampliación e implantación de la biblioteca EX5 de gestión de posiciones
Aprenda a importar y utilizar bibliotecas EX5 en su código o proyectos MQL5. En este artículo de continuación, ampliaremos la biblioteca EX5 agregando más funciones de gestión de posiciones a la biblioteca existente y creando dos Asesores Expertos. El primer ejemplo utilizará el indicador técnico de promedio dinámico de índice variable para desarrollar un asesor experto en estrategia comercial de trailing stop, mientras que el segundo ejemplo utilizará un panel comercial para monitorear, abrir, cerrar y modificar posiciones. Estos dos ejemplos demostrarán cómo utilizar e implementar la biblioteca de gestión de posiciones EX5 actualizada.
Algoritmo de optimización del comportamiento social adaptativo — Adaptive Social Behavior Optimization (ASBO): Método de Schwefel, método de Box-Muller Algoritmo de optimización del comportamiento social adaptativo — Adaptive Social Behavior Optimization (ASBO): Método de Schwefel, método de Box-Muller
Este artículo presenta una fascinante inmersión en el mundo del comportamiento social de los organismos vivos y su influencia en la creación de un nuevo modelo matemático, el ASBO (Adaptive Social Behavior Optimisation). Hoy exploraremos cómo los principios de liderazgo, vecindad y cooperación observados en las sociedades de seres vivos inspiran el desarrollo de algoritmos de optimización innovadores.
Creación de un modelo de restricción de tendencia de velas (Parte 7): Perfeccionamos nuestro modelo de desarrollo de la EA Creación de un modelo de restricción de tendencia de velas (Parte 7): Perfeccionamos nuestro modelo de desarrollo de la EA
En este artículo, profundizaremos en la preparación detallada de nuestro indicador para el desarrollo del Asesor Experto (EA). Nuestro debate abarcará mejoras adicionales en la versión actual del indicador para mejorar su precisión y funcionalidad. Además, introduciremos nuevas características que marcan puntos de salida, abordando una limitación de la versión anterior, que solo identificaba puntos de entrada.
Construya Asesores Expertos Auto-Optimizables con MQL5 y Python Construya Asesores Expertos Auto-Optimizables con MQL5 y Python
En este artículo, vamos a discutir cómo podemos construir Asesores Expertos capaces de seleccionar de forma autónoma y cambiar las estrategias de negociación en función de las condiciones imperantes en el mercado. Aprenderemos sobre las cadenas de Markov y cómo pueden sernos útiles como operadores algorítmicos.