English Русский 中文 Deutsch 日本語 Português
preview
Teoría de categorías en MQL5 (Parte 21): Transformaciones naturales con ayuda de LDA

Teoría de categorías en MQL5 (Parte 21): Transformaciones naturales con ayuda de LDA

MetaTrader 5Sistemas comerciales | 22 febrero 2024, 11:20
326 0
Stephen Njuki
Stephen Njuki

Introducción

Hasta ahora, hemos analizado bastantes aspectos de la teoría de categorías que pueden aplicarse fuera del ámbito académico, en particular: conjuntos y morfismos, conmutación, registros de ontología, productos, coproductos, límites, colímites, monoides, grupos, gráficos, órdenes, funtores y ahora transformaciones naturales. La teoría de categorías es mucho más amplia que todo lo que hemos abarcado y estos temas se han elegido por su facilidad de aplicación y uso en otras disciplinas matemáticas. Si está interesado en una discusión más detallada sobre el tema, le puedo sugerir este libro Muchos lo consideran la Biblia de la teoría de categorías.

Continuando el estudio de las transformaciones naturales, hoy veremos otras posibles aplicaciones en el pronóstico de series temporales. Las transformaciones naturales con frecuencia se pueden encontrar en conjuntos de datos relacionados, y por ahí es donde quiero comenzar este artículo.

Entonces, manos a la obra. La startup crea una base de datos para que sus clientes realicen un seguimiento de sus compras a lo largo del tiempo e inicialmente consta de, digamos, tres columnas. Clave principal, nombre del producto e importe pagado. Con el tiempo, la empresa nota repeticiones frecuentes en la columna de productos, lo cual significa que un producto en particular se compra con frecuencia. Así, se toma la decisión de comenzar a registrar más información relacionada con el producto para discernir mejor las preferencias de los clientes y posiblemente explorar la posibilidad de desarrollar nuevos productos. Para lograrlo, la columna del producto se divide en 3 columnas: versión, modo de suscripción y nombre de compilación. O una empresa podría querer dar más color a su información de pago y decidir dividir la columna de pago en tres partes, por ejemplo, para el modo de pago, la divisa (o ubicación) y el importe del pago. Nuevamente, esta división no será exhaustiva ya que quizá que se requieran más datos en una etapa futura dependiendo de las compras y preferencias de los clientes.

Cada una de estas nuevas columnas creadas se asignará a la columna del producto anterior, y si, por ejemplo, estableciéramos algunas correlaciones clave entre la columna anterior de un producto y, digamos, la columna del importe pagado o cualquier otra columna en la tabla de la base de datos, entonces restaurar esas correlaciones en la nueva estructura de la tabla supondría un proceso tedioso. Obviamente, hay muchas maneras en que una empresa puede resolver este problema, pero la transformación natural ofrece una buena solución.

Primero intentaremos considerar el problema como dos categorías. En la categoría de dominio tendremos un listado de las tablas de la empresa en su base de datos, y en la categoría de codominio tendremos dos versiones de las tablas con información del cliente. Para simplificar, si tomamos la lista como un objeto en el dominio y cada una de nuestras tablas como objetos separados en el codominio, entonces los dos funtores de la lista, uno para cada tabla, implicarán una conversión natural entre las dos tablas. Entonces, un funtor coincidirá con las tablas antiguas, que en nuestro caso será una tabla simple con tres columnas, mientras que el segundo funtor coincidirá con los cambios en la estructura de la tabla. Si es la versión 1, entonces el segundo funtor se asignará a una tabla de 5 columnas.

Usar una transformación natural no solo significará que podemos cuantificar las diferencias entre estas dos tablas usando una función algorítmica de mapeo como: ecuación lineal, ecuación cuadrática, perceptrón multicapa, bosque aleatorio o análisis discriminante lineal, sino también que podemos usar estos pesos para restaurar correlaciones anteriores con la tabla anterior y desarrollar otras nuevas para las columnas creadas.


Información general

Como bien recordamos, las transformaciones naturales suponen la diferencia entre los objetivos de dos funtores. El uso de esta diferencia se ha destacado en esta serie de artículos usando el cuadrado de la naturalidad, la composición conmutativa de objetos en la categoría codominio de funtores. Podrá encontrar una descripción en el artículo 18. También hemos abarcado la inducción del cuadrado de la naturalidad en el artículo 19.

El concepto de datos de series temporales no resulta ajeno a la mayoría de los tráders, ya que muchos de nosotros no solo estamos familiarizados con los gráficos de precios y los indicadores integrados en el terminal MetaTrader, sino que también desarrollamos nuestros propios indicadores y asesores. Algunos de los trabajos sobre estos temas, como este y este, les resultarán familiares a muchos. No obstante, si miramos el gráfico de precios de, digamos, un par de divisas, veremos que es discreto, ya que tenemos un precio específico en cada intervalo. Esto a pesar de que cuando los mercados están abiertos, que es la mayor parte de la semana, este precio siempre cambiará, lo cual significa que en realidad supone una serie temporal continua. Por tanto, para facilitar el análisis, usaremos una representación de series temporales discretas.

Por lo tanto, la capacidad de realizar análisis se basará en la existencia de precios "consensuados" para un valor determinado en un momento determinado. Al intentar realizar pronósticos, resulta importante estudiar diferentes series en diferentes periodos temporales. Es por eso que ver y comparar datos desfasados ​​puede considerarse un método más constructivo para obtener resultados precisos.

Entonces, como hemos dicho, en este artículo veremos dos conjuntos de datos. Serán similares, pero uno resultará más complejo que el otro. Realizaremos su transformación natural con un retraso para ayudar a hacer predicciones. Nuestros dos conjuntos de datos, que se pueden representar como tablas, serán muy simples. El primero contendrá los valores de la media móvil, mientras que el segundo contendrá los precios que componen esta media.


Descripción de los conjuntos de datos.

Entonces, una tabla simple tendrá solo dos columnas. La columna de marca temporal y la columna de media móvil. El número de filas en esta tabla y en la tabla compuesta se establecerá utilizando el parámetro de entrada m_data. Esta tabla simple se desplazará en el tiempo respecto a la tabla compuesta en el tamaño del periodo de media móvil utilizado. Entonces, si nuestro periodo de media móvil es 5, los valores en esta tabla serán las barras de los 5 tiempos de la tabla compuesta.

Una tabla compuesta retrasada también tendrá una columna de marca temporal y otras columnas, cada una de las cuales mostrará el precio en un momento diferente. El número de estas columnas adicionales además de la marca temporal será establecido por el periodo de la media móvil, por lo que, una vez más, si nuestra media móvil supera 5 barras de precios, entonces esta tabla tendrá una columna con una marca temporal y 5 columnas de precios.

Estos dos conjuntos de datos, que tienen una transformación natural, se pueden definir de varias maneras, que ya enumeramos en la introducción. En este artículo usaremos el método de análisis discriminante lineal (linear discriminant analysis, LDA). Ya hemos mostrado su uso con el wizard MQL5 y la biblioteca Alglib en este artículo, pero creo que le resultará útil repasar los puntos más importantes.

Encontrará una definición más específica aquí, pero en un sentido amplio, el LDA es un clasificador. Si observamos cualquier conjunto de datos de entrenamiento típico, este siempre tiene variables independientes (valores que se supone que influyen en el resultado final) y variables clasificadoras que sirven como "resultado final". Con el LDA, tendremos la capacidad de clasificar este resultado final en n clases, donde n será un número natural. Este algoritmo, desarrollado por Sir Ronald Fisher, genera un vector de pesos que ayuda a determinar el centroide principal de cada clasificador y también puede estimar la posición del centroide desconocido (punto de datos nuevo o desconocido). Con esta información, podremos simplemente comparar la posición del centroide desconocido con la posición del conocido para saber de cuál está más cerca y por tanto cuál es su clasificación. Para ilustrar la cuestión, el "vector de peso" se considerará como la ecuación de la línea que separa los puntos que tienen solo dos clasificadores. Si hay tres clasificadores, entonces esta será una ecuación plana. Si hay uno, entonces estas serán las coordenadas en la recta numérica. En cualquier escenario, podremos diferenciar entre el conjunto de datos de entrenamiento asignando a cada punto de datos un conjunto de coordenadas.


Transformación natural para el pronóstico de series temporales.

Entonces, como hemos mencionado en los artículos 18 y 19 anteriores, al analizar los cuadrados de la naturalidad, los objetos de codominio suelen ser lo único que importa y, por lo tanto, al mostrar cómo dos conjuntos de datos diferentes de precios y medias móviles pueden tener una transformación natural para este artículo, no hemos dicho nada sobre la categoría de la fuente ni los objetos del funtor. Su importancia no es crítica. No obstante, si tenemos varios objetos en la categoría original, podemos esperar que haya múltiples ejemplares proporcionales tanto del conjunto de datos simple como del compuesto. Ya lo señalamos al analizar la inducción del cuadrado de la naturalidad en el artículo 19.

Entonces, desde la perspectiva de la correspondencia de los gráficos, nuestra transformación natural no debería resultar demasiado complicada. Las columnas de marca temporal de los conjuntos de datos se vincularán y todas las columnas de precios del conjunto de datos compuesto se mapearán con la columna de media móvil en el conjunto de datos simple. Esto normalmente se representa en el código MQL5 con fines ilustrativos y lógicos, y para este propósito tenemos ejemplares de conjuntos de datos simples y compuestos declarados como "m_o_s" y "m_o_c", respectivamente. Estos ejemplares de clases ahora se denominan "objetos" en lugar de "dominios" porque el término "dominio" supone una propiedad de uno de los objetos relacionados con el morfismo y no será necesariamente un sustantivo en sí mismo. Lo que llamamos dominio en la mayoría de nuestros artículos anteriores se suele llamar objeto. Me abstuve de utilizar la palabra "objeto" para evitar confusiones con las clases integradas en MQL5. Este enfoque resulta menos propenso a errores de lógica que podrían cometerse fácilmente si fuéramos más directos y copiáramos los datos de precio directamente en nuestra función de mapeo. Este es el tipo de errores que no aparecen ni siquiera al probar el asesor, ya que se compila normalmente.

Por lo tanto, la transformación natural se implementará usando LDA, y el mapeo crítico se dará desde los puntos de precio retrasados hasta el valor futuro de la media móvil del precio. La columna de marca temporal no se utilizará, aunque se mencionará para que esté completa, de modo que el lector pueda comprender cómo están estructurados los datos. Como hemos indicado anteriormente, el retraso será igual a la duración del periodo de la media móvil. Por lo tanto, durante el entrenamiento usaremos precios que están a n barras del índice inicial, donde n será la duración del periodo de nuestra media móvil. Esto también significará que, al realizar pronósticos, obviamente estaremos usando los precios más recientes, pero nuestro pronóstico será para n barras en el futuro, no con la media móvil actual.


Uso de la transformación natural para realizar pronósticos

Entonces, el código para esto básicamente será controlado por dos funciones dentro de la clase de señal porque, como ya hemos dicho, no estamos codificando todas las estructuras de las clases que típicamente describen las categorías que son morfismos o funtores, como hicimos en el pasado; más bien, solo usaremos clases de objetos y elementos para representar lo que se encuentra en la categoría de codominio. El resultado final debería resultar idéntico al enfoque elegido aquí, ya que resulta más eficiente en el uso de recursos informáticos y, por lo tanto, más fácil de probar en un simulador de estrategias. Sin embargo, esto requiere mayor cuidado para evitar errores en la lógica de las categorías y sus funtores, ya que los errores cometidos pueden no aparecer al realizar la compilación o ejecutar las pruebas de la estrategia. La función de actualización tendrá el siguiente aspecto:

//+------------------------------------------------------------------+
//| Refresh function to update objects.                              |
//+------------------------------------------------------------------+
void CSignalCT::Refresh(int DataPoints=1)
   {
      m_time.Refresh(-1);
      m_close.Refresh(-1);
      
      for(int v=0;v<DataPoints;v++)
      {
         m_e_s.Let(); m_e_s.Cardinality(2);
         m_e_c.Let(); m_e_c.Cardinality(m_independent+1);
         
         m_e_s.Set(0,TimeToString(m_time.GetData(v)));
         m_e_c.Set(0,TimeToString(m_time.GetData(v)));
      
         double _s_unit=0.0;
         //set independent variables..
         for(int vv=0;vv<m_independent;vv++)
         {
            double _c_unit=m_close.GetData(StartIndex()+v+vv+m_independent);
            
            m_e_c.Set(vv+1,DoubleToString(_c_unit));
         }
         
         m_o_c.Set(0,m_e_c);
         
         //get dependent variable, the MA..
         for(int vv=v;vv<v+m_independent;vv++)
         {
            _s_unit+=m_close.GetData(StartIndex()+vv);
         }
         
         _s_unit/=m_independent;
         
         m_e_s.Set(1,DoubleToString(_s_unit));
         
         m_o_s.Set(0,m_e_s);
      }
   }


Esta función de actualización será llamada por la función get direction, cuya lista se verá así:

//+------------------------------------------------------------------+
//| Get Direction function from implied naturality square.           |
//+------------------------------------------------------------------+
double CSignalCT::GetDirection()
   {
      double _da=0.0;
      
      int _info=0;
      CMatrixDouble _w,_xy,_z;
      _xy.Resize(m_data,m_independent+1);
      
      double _point=0.00001;
      if(StringFind(m_symbol.Name(),"JPY")>=0){ _point=0.001; }
      
      for(int v=0;v<m_data;v++)
      {
         Refresh(v+1);
         
         ...
         
         //training classification
         _xy.Set(v,m_independent,(fabs(_ma-_lag_ma)<=m_regularizer*_point?1:(_ma-_lag_ma>0.0?2:0)));
      }
      
      m_lda.FisherLDAN(_xy,m_data,m_independent,__CLASSES,_info,_w);
      
      if(_info>0)
      {
         double _centroids[__CLASSES],_unknown_centroid=0.0; ArrayInitialize(_centroids,0.0);
         
         _z.Resize(1,m_independent+1);
         
         m_o_c.Get(0,m_e_c);
         
         for(int vv=0;vv<m_independent;vv++)
         {
            string _c="";
            m_e_c.Get(vv+1,_c);
            
            double _c_value=StringToDouble(_c);
            _z.Set(0,vv,_c_value);
         }
         
         for(int v=0;v<m_data;v++)
         {
            for(int vv=0;vv<m_independent;vv++)
            {
               _centroids[int(_xy[v][m_independent])]+= (_w[0][vv]*_xy[v][vv]);
            }
         }
         
         // best vector is the first 
         for(int vv=0;vv<m_independent;vv++){ _unknown_centroid+= (_w[0][vv]*_z[0][vv]); }
         
         
... 
      }
      else
      {
         
... 
      }
      
      return(_da);
   }

Nuestro pronóstico supondrá un cambio en el precio de la futura media móvil, por lo que un cambio negativo indicará un sentimiento bajista, un cambio positivo indicará un sentimiento alcista y ningún cambio indicará un nivel estable. Para cuantificar la ausencia de cambio, tenemos un parámetro regulador llamado m_regulizer, que establecerá el valor mínimo previsto en el que un cambio se considerará bajista o alcista. Este será un número entero que determinaremos multiplicándolo por el tamaño del punto del símbolo.

Bien, esta ha sido una descripción general rápida de nuestro código que implementa la transformación. La declaración de variables importantes se realizará, como siempre, en el manifiesto de la clase. Además de las declaraciones típicas para la clase de señal, para nuestras declaraciones de clase especial añadiremos ejemplares de los elementos simples y compuestos, así como un ejemplar de nuestra clase discriminante lineal.

Luego en cada nueva barra actualizaremos los valores de estos elementos y por lo tanto sus objetos usando la función de actualización. Esto implicará asignar variables independientes, es decir, simplemente asignar un número de precios igual a la duración del periodo de media móvil de entrada. Bien, vamos a transmitir estos precios al elemento compuesto y a los objetos. Usaremos 3 clasificadores para nuestro LDA: 2 para el mercado alcista, 1 para el mercado plano y 0 para el mercado bajista. Por lo tanto, a cada punto de datos de entrenamiento se le asignará una clasificación basada en la diferencia entre la media móvil actual (basada en el índice en el conjunto de entrenamiento) y la media móvil retrasada. Ambas medias se tomarán con la misma longitud, que ya hemos mencionado como datos de entrada, y el retraso también será igual a esta longitud.

La asignación de clasificadores se entrenará como parte de la implementación del análisis discriminante lineal en Alglib. También podría valer la pena mencionar nuestro modo de regularización, que simplemente determinará qué señales ignorar, es decir, qué clasificar como ruido blanco. Para responder a esta pregunta, tomaremos cualquier diferencia entre dos medias móviles que sea inferior al parámetro de entrada m_regularizer, que será un número entero que multiplicaremos por el tamaño del pip del símbolo para hacerlo comparable con la dispersión de las medias móviles del precio.

Al hacerlo, ejecutaremos la función de Fisher para generar una matriz de coeficientes (o pesos) w, que, como ya hemos mencionado, forma la ecuación para el plano definitorio entre nuestros clasificadores.

La matriz Z, que representa los precios actuales para el próximo pronóstico, se rellenará con el último array de precios y luego se le asignará el producto puntual con la matriz w de la función de Fisher para obtener el valor de su centroide definido por la matriz w. Este valor será nuestro centroide desconocido.

De forma similar, los valores del centroide de nuestros tres clasificadores también se rellenarán con el producto escalar de esta matriz con la matriz de variables independientes.

Una vez obtenidos los tres valores del centroide del clasificador y el valor del centroide de nuestra incógnita, ahora la cuestión será comparar esta incógnita con los tres clasificadores y descubrir cuál es el más cercano a nuestra incógnita.


Uso práctico

Como ejemplo, probaremos el par GBPUSD desde principios de este año hasta el primero de junio. El informe se presenta a continuación:

r1


Desplazándonos hacia adelante hasta agosto, obtenemos resultados negativos, que se resumen en el siguiente informe:

r2


La precisión del pronóstico basado en el informe de pronóstico parece quedar bajo duda, lo cual puede deberse a una ejecución de optimización incompleta (realizada solo para las tres primeras generaciones) o a una ventana de prueba demasiado pequeña, ya que solo hemos analizado tres años, mientras que los sistemas fiables requieren periodos más largos. Adjuntamos el código fuente.


Conclusión

En la vida cotidiana, muy a menudo las tablas de nuestras bases de datos o los formatos de almacenamiento de datos crecen no solo en tamaño, sino también en complejidad. Esto hemos demostrado aquí, analizando un conjunto de datos que se vuelve más complejo con la adición de columnas de datos. Hemo intentado utilizar esto a nuestro favor estudiando conjuntos de datos distribuidos en el tiempo para hacer predicciones. Aunque los resultados de la optimización han mostrado potencial teniendo en cuenta solo tres generaciones, los resultados posteriores han quedado lejos de lo ideal. Esto se puede corregir combinando esta clase de señales con otra o realizando pruebas más exhaustivas durante periodos más prolongados.

Respecto al tema del artículo, las transformaciones naturales resultan bastante buenas para manejar diferentes estructuras de datos, no solo en los casos en los que el conjunto de datos evoluciona debido a necesidades del negocio o el análisis, sino quizás en los casos en los que se realizan comparaciones y los tamaños (el número de columnas) de ambos conjuntos de datos no son iguales. Esta característica sin duda resultará útil en varias disciplinas.


Enlaces

Los enlaces, como siempre, se relacionan principalmente con artículos de Wikipedia.

El archivo de señal adjunto deberá compilarse utilizando el wizard MQL5. Este artículo servirá de ayuda a quienes no estén familiarizados con las clases del wizard.


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

Archivos adjuntos |
ct_21.mqh (31.39 KB)
SignalCT_21_r2.mqh (11.52 KB)
Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD (Parte 3) Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD (Parte 3)
El presente artículo supone la tercera parte de la serie que describe las etapas de desarrollo de un cliente MQL5 nativo para el protocolo MQTT. En esta ocasión, hablaremos con detalle sobre la aplicación de un desarrollo basado en pruebas para implementar el intercambio de paquetes CONNECT/CONNACK. Al final de este paso, nuestro cliente DEBERÁ poder comportarse adecuadamente al lidiar con cualquier posible resultado del servidor al intentar conectarse.
Algoritmos de optimización de la población: Algoritmo de salto de rana aleatorio (Shuffled Frog-Leaping, SFL) Algoritmos de optimización de la población: Algoritmo de salto de rana aleatorio (Shuffled Frog-Leaping, SFL)
El artículo presenta una descripción detallada del algoritmo de salto de rana aleatorio (SFL) y sus capacidades para resolver problemas de optimización. El algoritmo SFL se inspira en el comportamiento de las ranas en su entorno natural y ofrece un enfoque innovador para la optimización de características. El algoritmo SFL supone una herramienta eficaz y flexible que puede gestionar una gran variedad de tipos de datos y alcanzar soluciones óptimas.
Algoritmos de optimización de la población: Algoritmo Mind Evolutionary Computation (Computación Evolutiva Mental, (MEC) Algoritmos de optimización de la población: Algoritmo Mind Evolutionary Computation (Computación Evolutiva Mental, (MEC)
En este artículo, analizaremos un algoritmo de la familia MEC llamado algoritmo MEC Simple de evolución mental (Simple MEC, SMEC). El algoritmo se caracteriza por la belleza de la idea expuesta y su sencillez de aplicación.
Teoría de categorías en MQL5 (Parte 20): Autoatención y transformador Teoría de categorías en MQL5 (Parte 20): Autoatención y transformador
Hoy nos apartaremos un poco de nuestros temas habituales y veremos parte del algoritmo de ChatGPT. ¿Tiene alguna similitud o concepto tomado de las transformaciones naturales? Intentaremos responder estas y otras preguntas usando nuestro código en formato de clase de señal.