Русский Português
preview
Redes neuronales en el trading: Sistema multiagente con validación conceptual (FinCon)

Redes neuronales en el trading: Sistema multiagente con validación conceptual (FinCon)

MetaTrader 5Sistemas comerciales |
83 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Introducción

Los mercados financieros se caracterizan por su gran volatilidad y complejidad, lo cual plantea serios problemas a la hora de tomar decisiones de inversión óptimas. Los tráders y gestores de portafolios deben tener en cuenta diversos datos multimodales, como indicadores macroeconómicos, indicadores técnicos y aspectos del comportamiento de los participantes en el mercado. El principal objetivo de estos esfuerzos consiste en maximizar la rentabilidad minimizando el riesgo.

En las empresas financieras tradicionales, son muchos los distintos especialistas que intervienen en el procesamiento de los datos y la toma de decisiones: los analistas investigan la información del mercado, los gestores de riesgos evalúan las amenazas potenciales y los ejecutivos toman decisiones estratégicas. Sin embargo, a pesar de la estructura jerárquica, el factor humano y la limitación en los recursos plantean dificultades para adaptarse rápidamente a los rápidos cambios del mercado. En este sentido, cada vez resulta más relevante el uso de sistemas automatizados que no solo agilicen el proceso de análisis, sino que también reduzcan la probabilidad de errores.

La investigación actual en inteligencia artificial y tecnología financiera se centra en el desarrollo de soluciones programáticas adaptables. Estos sistemas son capaces de aprender de los datos históricos, identificando patrones de mercado y tomando decisiones más informadas. Una de las áreas clave de la investigación reciente es la integración de técnicas de procesamiento del lenguaje natural (PLN) que analizan noticias financieras, previsiones de expertos y otros datos textuales para realizar previsiones y evaluaciones de riesgo más precisas.

El rendimiento de estos sistemas depende esencialmente de dos aspectos clave: la interacción entre los componentes individuales del sistema y su capacidad de autoaprendizaje continuo. La investigación demuestra que los sistemas que modelan el trabajo en equipo entre profesionales demuestran un mayor rendimiento y, al introducir nuevos enfoques, los modelos se adaptan mejor a las condiciones cambiantes.

Ejemplos de soluciones existentes similares a FinMem y FinAgent muestran avances significativos en la automatización de las transacciones financieras. Sin embargo, estos sistemas tienen desventajas: tienden a incidir más en los aspectos a corto plazo del mercado sin ofrecer soluciones globales para la gestión del riesgo a largo plazo. Además, la limitación de los recursos informáticos y la insuficiente flexibilidad de los algoritmos pueden reducir la calidad de sus recomendaciones.

Los autores del artículo "FinCon: A Synthesized LLM Multi-Agent System with Conceptual Verbal Reinforcement for Enhanced Financial Decision Making". En él propone el framework FinCon, que supone un sistema multiagente diseñado específicamente para integrar los procesos comerciales de las acciones y la gestión de portafolios.

Los agentes estructurados del framework FinCon modelan el trabajo de un equipo de especialistas. Los agentes analíticos recopilan y analizan datos de diversas fuentes, como los indicadores de mercado, los flujos de noticias y los datos históricos, mientras que los agentes de gestión resumen los resultados y desarrollan las soluciones necesarias. Este enfoque minimiza las comunicaciones redundantes entre los participantes en el proceso y optimiza el coste de los recursos informáticos.

Los autores del framework FinCon prevén la posibilidad de usarlo tanto para trabajar con un único activo financiero como para la gestión compleja de un portafolio de activos. Y esto hace que el sistema sea versátil.

FinCon realiza evaluaciones del riesgo en tiempo real utilizando modelos analíticos y algoritmos de aprendizaje automático. Una vez concluidas las operaciones, el sistema realiza análisis postoperativos, identificando errores y actualizando sus modelos a partir de los nuevos datos.

Las investigaciones realizadas por los autores del framework han demostrado que FinCon mejora sustancialmente los resultados de la gestión del riesgo y aumenta el rendimiento global de los portafolios.


La arquitectura de FinCon

La arquitectura de FinCon tiene una jerarquía de dos niveles e incluye dos componentes clave: el grupo de agentes "Gestor-Analista" y el sistema "Control de Riesgos". Esta estructura garantiza un procesamiento eficaz de la información, reduciendo la probabilidad de errores, minimizando el coste de la toma de decisiones y proporcionando un análisis en profundidad de los datos del mercado.

El framework FinCon se asemeja a una empresa de inversión bien organizada en la que todos los recursos se optimizan para lograr la máxima eficiencia. El objetivo principal es mejorar la percepción y el análisis de la información minimizando los costes de comunicación y el procesamiento de datos.

Los analistas de agentes desempeñan un papel fundamental en FinCon, destacando ideas de inversión clave partiendo de ingentes cantidades de datos de mercado. Cada agente tiene una especialización limitada, lo cual evita la sobrecarga de información y la duplicación de datos. La aplicación del framework por parte del autor usa 7 agentes analíticos. Los agentes de texto analizan artículos de prensa, comunicados e informes financieros para identificar posibles riesgos y oportunidades. Los agentes de audio interpretan las grabaciones de llamadas telefónicas disponibles sobre los beneficios de la gestión de las compañías de los activos analizados, destacando las connotaciones emocionales y los aspectos clave de las conversaciones. Los analistas de datos y los agentes de selección de valores calculan las métricas que permiten a los gestores predecir la dinámica del mercado con gran precisión.

Los agentes analíticos proporcionan los datos al gestor, que es el único sujeto de las decisiones comerciales. Para tomar decisiones comerciales, el gestor realiza 4 funciones clave: consolida los resultados de los analistas, controla oportunamente el riesgo, analiza continuamente las decisiones anteriores y afina sus convicciones de inversión.

El framework FinCon aplica un mecanismo de control de riesgos de dos niveles. La evaluación intraepisódica de riesgos le permite ajustar rápidamente las acciones en tiempo real, minimizando las pérdidas a corto plazo. El control de riesgos entre episodios, por su parte, se encarga de comparar los resultados de distintos episodios para ayudar a identificar errores y mejorar las estrategias. Este planteamiento garantiza la robustez del modelo ante los cambios externos y contribuye a su mejora continua.

La actualización de las convicciones de inversión desempeña un papel clave en la adaptación de las políticas de comportamiento del modelo. Permite al modelo ajustar el énfasis en las etapas de extracción de información por parte de los analistas y la posterior toma de decisiones por parte del gestor. El mecanismo Actor-Critic ayuda a FinCon a optimizar periódicamente sus planteamientos de inversión para cumplir los objetivos comerciales especificados. Este enfoque se basa en el análisis tanto de los éxitos como de los fracasos, lo cual facilita la mejora continua de la estrategia.

La reflexión episódica en el sistema FinCon se alimenta de un mecanismo único de refuerzo verbal conceptual (CVRF). Este componente analiza los resultados de episodios sucesivos comparando la información ofrecida por los analistas con las decisiones del gestor. El CVRF vincula los resultados clave a aspectos concretos de la estrategia comercial. Comparando los conceptos de los episodios más y menos exitosos, el modelo genera recomendaciones para ajustar las creencias de inversión. Esto ayuda a los agentes a centrarse en la información más relevante del mercado para mejorar la rentabilidad global.

Las recomendaciones de ajuste de las convicciones se transmiten primero al gestor y luego se distribuyen selectivamente a los analistas. Así se minimizan las interacciones redundantes y se evita la sobrecarga de información. Este método consiste en medir la proporción de acciones comerciales que coinciden entre dos trayectorias de aprendizaje consecutivas y permite aumentar la eficacia del sistema. Esto resulta especialmente notable en un entorno en el que cada agente tiene un papel bien definido y especializado, lo que favorece la sinergia en el rendimiento del modelo.

El framework FinCon incluye un algoritmo de módulo de memoria avanzado que se divide en tres componentes clave: la memoria de trabajo, la memoria procedimental y la memoria episódica.

La memoria de trabajo se utiliza para almacenar temporalmente los datos necesarios para realizar las operaciones en curso. Esto permite procesar rápidamente grandes cantidades de información sin perder el contexto.

La memoria procedimental almacena los algoritmos y estrategias que se han aplicado con éxito en episodios anteriores. Esta garantiza que el sistema se adapte rápidamente a tareas repetitivas y permite utilizar métodos probados en la resolución de problemas similares.

La memoria episódica captura eventos, acciones y resultados clave, lo cual resulta especialmente importante para analizar y ajustar la política a un nivel superior. Este componente de la memoria juega un papel fundamental en el aprendizaje de modelos, ya que permite incorporar los aciertos y errores del pasado para mejorar el rendimiento futuro.

La estructura escalonada hace de FinCon un sistema eficaz y adaptable. A continuación le mostramos la visualización del framework realizada por el autor.

Visualización del autor del framework FinCon


Implementación usando MQL5

Tras repasar los aspectos teóricos del framework FinCon, pasaremos a la parte práctica de nuestro artículo, donde implementaremos nuestra visión de los enfoques propuestos mediante MQL5.

Cabe señalar que la aplicación del framework FinCon por parte del autor se basa en el uso de grandes modelos lingüísticos previamente formados. Pero nosotros, al igual que antes, no utilizaremos modelos lingüísticos en nuestro trabajo, sino que aplicaremos los planteamientos propuestos con los medios de que disponemos. Y empezaremos nuestro trabajo actualizando el módulo de memoria.

Enfoques para modernizar un módulo de memoria


Como ya sabe, nuestro bloque de memoria desarrollado anteriormente incluye dos módulos de recurrencia de arquitecturas diferentes. Esto nos permitió crear un sistema de memoria de dos niveles con distintos ritmos de olvido de la información, lo que lo hizo más flexible y adaptable a distintas tareas. Este enfoque resulta especialmente útil en situaciones en las que hay que tener en cuenta tanto las dependencias a corto como a largo plazo.

Debemos señalar que en nuestro modelo, la tasa de olvido no se especifica directamente, sino que varía, debido a las diferencias en la arquitectura de los módulos de recurrencia utilizados. Uno de los módulos se centrará en la memoria a corto plazo, proporcionando actualizaciones rápidas de la información, mientras que el otro se centrará en las dependencias a largo plazo, permitiendo retener datos significativos durante largos periodos de tiempo. Estas diferencias arquitectónicas crearán un equilibrio natural entre la velocidad de procesamiento de los datos y la profundidad del análisis.

El almacenamiento de información en el bloque de memoria se realizará mediante estados ocultos de módulos de recurrencia. Esta forma de almacenar los datos puede reducir considerablemente el espacio de memoria, y además creará problemas cuando sea necesario recuperar y comparar episodios concretos. Y esto resulta crítico en el contexto de la aplicación del mecanismo de validación conceptual, que requiere la capacidad de comparar la situación actual con episodios almacenados previamente.

Asimismo, los autores del framework FinCon subrayan la importancia de utilizar un sistema de memoria de tres niveles para lograr análisis y predicciones más precisos. Esto plantea la necesidad de mejorar el bloque de memoria existente añadiendo una capa adicional de almacenamiento de información episódica.

El proceso de optimización de un bloque de memoria implica varios pasos importantes. En primer lugar, resulta necesario minimizar el uso de recursos informáticos. Esto puede lograrse aplicando algoritmos de compresión de datos. Dichos algoritmos eliminarán la información redundante reduciendo la cantidad de memoria necesaria para almacenar un único episodio, al tiempo que preservan las características clave de los datos.

Otro reto importante consiste en proporcionar un acceso rápido y preciso a la información pertinente. Para resolverlo, resultará razonable utilizar algoritmos de similitud vectorial. Estos algoritmos pueden hallar rápidamente los episodios más similares en la memoria, lo cual es especialmente importante para las tareas en tiempo real.

Como resultado, la unidad de memoria actualizada será la base para mejorar la eficiencia general del modelo. No solo posibilitará un almacenamiento de datos compacto, sino también un acceso rápido a la información necesaria, lo que mejorará notablemente el proceso de toma de decisiones.

Implementación del nuevo módulo de memoria


Implementaremos los enfoques propuestos dentro del nuevo objeto CNeuronMemoryDistil, cuya estructura se presentará a continuación.

class CNeuronMemoryDistil  :  public CNeuronMemory
  {
protected:
   CNeuronBaseOCL       cConcatenated;
   CNeuronTransposeOCL  cTransposeConc;
   CNeuronConvOCL       cConvolution;
   CNeuronEmbeddingOCL  cDistilMemory;
   CNeuronRelativeCrossAttention cCrossAttention;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronMemoryDistil(void) {};
                    ~CNeuronMemoryDistil(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count,
                          uint heads, uint stack_size,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override
 const   {  return defNeuronMemoryDistil; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  };

Como clase padre, utilizaremos el objeto de memoria creado anteriormente, del que heredaremos todas las interfaces básicas y los dos módulos recurrentes de memoria a corto y largo plazo. Los objetos internos declarados dentro de nuestra nueva clase sentarán las bases para la construcción de la memoria episódica. Estos permiten almacenar y procesar datos que representan episodios concretos. Cada objeto realizará una tarea específica dentro de la estructura general de la memoria, lo que facilitará un análisis de datos más preciso y exhaustivo. Trataremos su funcionalidad con más detalle cuando implementemos los métodos de este bloque de memoria.

Todos los objetos internos se declararán estáticamente, lo cual nos permitirá dejar vacíos el constructor y el destructor de la clase. Este enfoque optimizará el uso de la memoria y mejorará la estabilidad del sistema minimizando el riesgo de errores al crear o destruir objetos.

La inicialización de todos los objetos heredados y recién declarados se realizará en el método Init. Los parámetros del método incluyen un conjunto de constantes que definirán inequívocamente la arquitectura del objeto a crear y proporcionarán la flexibilidad necesaria para personalizar el modelo para tareas específicas, manteniendo su integridad y funcionalidad.

bool CNeuronMemoryDistil::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                               uint window, uint window_key, uint units_count,
                               uint heads, uint stack_size,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronRelativeCrossAttention::Init(numOutputs, myIndex, open_cl, window,
                                         window_key, units_count, heads, window,
                                       2 * stack_size, optimization_type, batch))
      return false;

En el cuerpo del método, solemos llamar al método homónimo de la clase padre para inicializar los objetos heredados pasando los parámetros correspondientes. Sin embargo, en este caso, debido a las características únicas del nuevo objeto, no podremos utilizar directamente el método de inicialización del módulo de memoria básica. En su lugar, la solución óptima será utilizar un método similar del objeto de atención cruzada que será padre del módulo de memoria básica. Así se mantendrá la coherencia y se garantizará que todos los elementos de la nueva arquitectura funcionen correctamente.

Mirando un poco más adelante, cabe señalar que a la salida de nuestro módulo de memoria, presupondremos un análisis del estado actual en el contexto de la memoria episódica. En este proceso, la unidad de atención cruzada desempeñará un papel clave, cumpliendo dos funciones principales. La primera será encontrar los episodios más relevantes. Para llevar a cabo esta tarea, se utilizarán algoritmos de similitud vectorial que sustentan la determinación de los coeficientes de dependencia dentro del mecanismo de atención.

La segunda función del bloque de atención cruzada consistirá en enriquecer el estado inicial con el contexto de los episodios seleccionados. Esto contribuye a obtener una imagen más completa e informativa, lo que a su vez mejora la calidad del procesamiento de los datos y la precisión de la toma de decisiones.

Por lo tanto, cuando llamemos al método de inicialización de la clase padre, para el flujo de información principal, especificaremos los parámetros de los datos de origen recibidos, mientras que la longitud de la secuencia de contexto será 2 veces el búfer de la memoria de episodios creada. El uso del doble tamaño del búfer de datos se debe a que se guardarán los resultados de la memoria a corto y largo plazo.

Tras realizar con éxito las operaciones del método de inicialización del objeto de atención cruzada, llamaremos a los métodos homónimos de los módulos heredados de memoria recurrente a corto y largo plazo. Este proceso se trasladará completamente desde el método de la clase padre.

   uint index = 0;
   if(!cLSTM.Init(0, index, OpenCL, iWindow, iUnits, optimization, iBatch))
      return false;
   index++;
   if(!cMamba.Init(0, index, OpenCL, iWindow, 2 * iWindow, iUnits, optimization,
                                                                        iBatch))
      return false;

Luego concatenaremos los resultados de los módulos de recurrencia en un único búfer para organizar la posibilidad de acceder simultáneamente a los datos de la memoria a largo y corto plazo. Para ello, crearemos un objeto básico de tamaño suficiente.

   index++;
   if(!cConcatenated.Init(0, index, OpenCL, 2 * iWindow * iUnits, optimization,
                                                                       iBatch))
      return false;
   cConcatenated.SetActivationFunction(None);

El siguiente paso consistirá en comprimir la información adquirida e integrarla en el objeto de almacenamiento de la memoria episódica.

Aquí cabe señalar que estaremos trabajando con una representación de una serie temporal multimodal que describirá un único estado del entorno. Esto implicará la necesidad de preservar las características clave de las secuencias unitarias durante la compresión de datos. Para lograr este objetivo, hemos desarrollado un método de compresión de datos en dos etapas. En la primera etapa se llevará a cabo la compresión preliminar de la información en el marco de secuencias unitarias separadas, lo que permitirá destacar sus características principales y reducir el volumen de datos sin que su integridad sufra pérdidas.

Primero transpondremos el tensor de datos concatenados para representarlos como secuencias unitarias.

   index++;
   if(!cTransposeConc.Init(0, index, OpenCL, iUnits, 2 * iWindow, optimization,
                                                                       iBatch))
      return false;

Y luego comprimiremos los datos utilizando las herramientas de la capa convolucional.

   index++;
   if(!cConvolution.Init(0, index, OpenCL, iUnits, iUnits, iWindowKey, iWindow,
                                                      1, optimization, iBatch))
      return false;
   cConvolution.SetActivationFunction(GELU);

Para almacenar los datos de la memoria episódica, aplicaremos una capa de incorporación que posibilitará la organización eficiente de una pila de memoria de longitud fija implementada según el principio FIFO (First In, First Out).

Además, esta capa incluirá la funcionalidad de proyectar bloques individuales del tensor de datos de origen en el subespacio latente, lo que se logrará utilizando matrices de proyección individuales entrenadas. Estas matrices permitirán transformar datos multimodales de su formato original a una representación unificada. Este enfoque simplificará los análisis posteriores y garantizará la coherencia de los datos al integrar información procedente de distintas fuentes. En este caso, se tratará de módulos de memoria a corto y largo plazo.

   index++;
   uint windows[] = {iWindowKey * iWindow, iWindowKey * iWindow};
   if(!cDistilMemory.Init(0, index, OpenCL, iUnitsKV / 2, iWindow, windows))
      return false;

Como ya hemos mencionado, los análisis de los datos de origen en el contexto de la memoria episódica se realizarán utilizando un objeto básico de atención cruzada. Sin embargo, para un análisis más completo, será necesario introducir otro mecanismo de atención cruzada que se centre en el procesamiento de los datos de origen en el contexto de la memoria a corto y largo plazo. Este enfoque permitirá considerar el efecto sinérgico de la combinación de distintos tipos de memoria.

Recordemos que anteriormente hemos creado un objeto de concatenación para concatenar los datos de la memoria a corto y largo plazo, lo que simplificará su proceso de integración. Como consecuencia, un único objeto de atención cruzada podrá enriquecer eficazmente los datos de origen con contexto, considerando simultáneamente la información almacenada en los dos módulos de memoria. Esta solución optimizará los recursos informáticos y mejorará la precisión del análisis.

   index++;
   if(!cCrossAttention.Init(0, index, OpenCL, iWindow, iWindowKey, iUnits, iHeads,
                                       iWindow, 2 * iUnits, optimization, iBatch))
      return false;
//---
   return true;
  }

Tras inicializar todos los objetos internos, solo deberemos devolver el resultado lógico de las operaciones al programa que realiza la llamada y finalizar el método.

A continuación, construiremos el método de pasada directa de nuestro nuevo bloque de memoria como parte del método feedForward.

bool CNeuronMemoryDistil::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cLSTM.FeedForward(NeuronOCL))
      return false;
   if(!cMamba.FeedForward(NeuronOCL))
      return false;

En los parámetros del método, obtendremos el puntero al objeto de datos de origen, que pasaremos inmediatamente a los métodos homónimos de nuestros módulos de memoria recurrente a corto y largo plazo. Luego concatenaremos los resultados de su trabajo en un único búfer de datos.

   if(!Concat(cLSTM.getOutput(), cMamba.getOutput(), cConcatenated.getOutput(),
                                                     iWindow, iWindow, iUnits))
      return false;
   if(!cTransposeConc.FeedForward(cConcatenated.AsObject()))
      return false;

Nótese que concatenaremos los datos en cuanto a pasos temporales individuales. Esto nos permitirá preservar la estructura de todas las secuencias unitarias de los resultados de ambos módulos de memoria en el tensor final.

Después transpondremos el tensor obtenido para una mayor compresión de los datos dentro de secuencias unitarias individuales mediante la capa de convolución.

   if(!cConvolution.FeedForward(cTransposeConc.AsObject()))
      return false;

Los resultados de la compresión previa se transmitirá a la capa de incorporación, donde los estados del entorno parcialmente comprimidos se proyectarán en una representación latente más compacta y se añadirán a la pila de memoria episódica.

   if(!cDistilMemory.FeedForward(cConvolution.AsObject()))
      return false;

En esta fase, hemos implementado el almacenamiento de la cantidad necesaria de información en los tres niveles de nuestra unidad de memoria y ahora solo quedará enriquecer el estado actual del entorno con el contexto de los acontecimientos clave. En primer lugar, analizaremos los datos de origen en el contexto de la memoria a corto y largo plazo.

   if(!cCrossAttention.FeedForward(NeuronOCL, cConcatenated.getOutput()))
      return false;

Y luego enriqueceremos la información con el contexto de la memoria episódica.

   return CNeuronRelativeCrossAttention::feedForward(cCrossAttention.AsObject(),
                                                     cDistilMemory.getOutput());
  }

Luego transmitiremos el resultado lógico de la ejecución de las operaciones al programa que realiza la llamada y finalizaremos el método.

A la construcción del método de pasada directa le seguirá la aplicación de los algoritmos de pasada inversa. Como ya sabe, aquí se supone que se construirán dos métodos:

  • la distribución del gradiente de error calcInputGradients;
  • la actualización de los parámetros del modelo updateInputWeights.

El flujo de datos en el método de distribución del gradiente de error será completamente idéntico al del algoritmo de pasada directa, pero se realizará en orden inverso.

En los parámetros del método calcInputGradients obtendremos el puntero al mismo objeto de datos de origen, solo que esta vez tendremos que transmitirle el gradiente de error correspondiente a su influencia en el resultado final del modelo.

bool CNeuronMemoryDistil::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

En el cuerpo del método comprobaremos inmediatamente la relevancia del puntero recibido, porque de lo contrario todas las operaciones posteriores del método carecerán de sentido.

El método de pasada directa finalizará llamando al método homónimo de la clase padre de atención cruzada. Como consecuencia, las operaciones de distribución del gradiente de error partirán del método correspondiente del mismo objeto. En este caso, distribuiremos el gradiente de error sobre los flujos de información de los niveles de memoria.

  if(!CNeuronRelativeCrossAttention::calcInputGradients(cCrossAttention.AsObject(),
                                                         cDistilMemory.getOutput(),
                                                       cDistilMemory.getGradient(),
                                      (ENUM_ACTIVATION)cDistilMemory.Activation()))
      return false;

Después iteraremos el gradiente de error de la memoria episódica a través de los objetos de compresión de datos hasta el nivel de objeto de concatenación de la memoria a corto y largo plazo.

   if(!cConvolution.calcHiddenGradients(cDistilMemory.AsObject()))
      return false;
   if(!cTransposeConc.calcHiddenGradients(cConvolution.AsObject()))
      return false;
   if(!cConcatenated.calcHiddenGradients(cTransposeConc.AsObject()))
      return false;

Sin embargo, el objeto de concatenación también se utilizará al analizar los datos de origen en el contexto de la memoria a corto y largo plazo. Como consecuencia, tendremos que transmitir el gradiente de error al objeto especificado y por el segundo flujo de información.

   if(!NeuronOCL.calcHiddenGradients(cCrossAttention.AsObject(),
                                     cConcatenated.getOutput(),
                                     cConcatenated.getPrevOutput(),
                                     (ENUM_ACTIVATION)cConcatenated.Activation()))
      return false;

Nótese que en este caso, para obtener el gradiente de error, utilizaremos el búfer libre del objeto de concatenación de datos en lugar del búfer especializado. Esto se relaciona con la necesidad de preservar los datos adquiridos previamente.

A continuación, sumaremos los valores obtenidos de los dos flujos de información y asignaremos los datos obtenidos a los objetos de memoria correspondientes.

   if(!SumAndNormilize(cConcatenated.getGradient(), cConcatenated.getPrevOutput(),
                       cConcatenated.getGradient(), iWindow, false, 0, 0, 0, 1) ||
      !DeConcat(cLSTM.getGradient(), cMamba.getGradient(), 
                cConcatenated.getGradient(), iWindow, iWindow, iUnits))
      return false;

Nótese que aquí no utilizamos intencionadamente la función de activación para el objeto de concatenación para evitar distorsionar los datos. De hecho, los objetos de la memoria a corto y largo plazo pueden tener funciones de activación distintas. Sin embargo, tras distribuir los gradientes de error entre los objetos, deberemos comprobar la existencia de la función de activación de los objetos correspondientes y, de ser necesario, ajustar los valores de gradiente usando las derivadas de las funciones de activación.

   if(cLSTM.Activation() != None)
      if(!DeActivation(cLSTM.getOutput(), cLSTM.getGradient(), 
                       cLSTM.getGradient(), cLSTM.Activation()))
         return false;
   if(cMamba.Activation() != None)
      if(!DeActivation(cMamba.getOutput(), cMamba.getGradient(), 
                       cMamba.getGradient(), cMamba.Activation()))
         return false;

Ahora solo quedará reducir el gradiente de error a lo largo de las líneas troncales de la memoria a corto y largo plazo hasta el nivel de los datos de origen. Sin embargo, deberemos recordar que ya hemos transferido datos al búfer de gradiente de error de este objeto a través de la línea troncal de análisis de información en el contexto de los niveles de memoria. Por consiguiente, para conservar los valores obtenidos anteriormente, necesitaremos sustituir los punteros a los búferes de datos antes de realizar otras operaciones.

   CBufferFloat *temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(cConcatenated.getPrevOutput(), false) ||
      !NeuronOCL.calcHiddenGradients(cLSTM.AsObject()) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, iWindow,
                                                  false, 0, 0, 0, 1))
      return false;

A continuación, haremos descender secuencialmente los errores a lo largo de los flujos de información de los módulos de memoria correspondientes añadiendo obligatoriamente los valores obtenidos a los datos previamente acumulados.

   if(!NeuronOCL.calcHiddenGradients(cMamba.AsObject()) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, iWindow,
                                               false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

Luego devolveremos a su estado original los punteros a los búferes de datos. Después pasaremos el resultado lógico de las operaciones al programa que realiza la llamada y finalizaremos el método.

Con esto concluirá nuestra revisión de los métodos del bloque de memoria CNeuronMemoryDistil actualizado. Podrá leer el código completo de este objeto y todos sus métodos por sí mismo en el archivo adjunto.

La siguiente etapa de nuestro trabajo consistirá en construir el objeto de Agente-Analista. Sin embargo, ya casi hemos finalizado el artículo. Así que haremos una breve pausa y continuaremos en el próximo artículo.


Conclusión

En este artículo nos hemos familiarizado con el framework FinCon, un innovador sistema multiagente diseñado para mejorar la toma de decisiones financieras. El framework integra una estructura jerárquica de interacción «Gestor-Analista» y mecanismos conceptuales de refuerzo verbal, garantizando así una interacción coordinada de los agentes y una gestión eficaz de los riesgos. Estas características permiten al modelo gestionar con éxito una gran variedad de tareas financieras, mostrando una gran adaptabilidad y rendimiento en un entorno financiero dinámico.

En la parte práctica de nuestro trabajo hemo comenzado a aplicar los enfoques propuestos usando MQL5. Este trabajo continuará en el próximo artículo, al final del cual analizaremos la eficacia de las soluciones aplicadas con datos históricos reales.


Enlaces


Programas usados en el artículo

# Nombre Tipo Descripción
1 Research.mq5 Asesor Asesor de recopilación de datos
2 ResearchRealORL.mq5
Asesor
Asesor de recopilación de ejemplos con el método Real-ORL
3 Study.mq5 Asesor Asesor de entrenamiento de modelos
4 Test.mq5 Asesor Asesor para la prueba de modelos
5 Trajectory.mqh Biblioteca de clases Estructura de descripción del estado del sistema y la arquitectura del modelo
6 NeuroNet.mqh Biblioteca de clases Biblioteca de clases para crear una red neuronal
7 NeuroNet.cl Biblioteca Biblioteca de código del programa OpenCL

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/16916

Archivos adjuntos |
MQL5.zip (2352.32 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Del básico al intermedio: Estructuras (VI) Del básico al intermedio: Estructuras (VI)
En este artículo, veremos cómo podemos empezar a implementar una base de código estructural genérico. El objetivo es reducir nuestro trabajo a la hora de programar y aprovechar todo el potencial que ofrece el propio lenguaje de programación. En este caso, MQL5.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Cliente en Connexus (Parte 7): Añadir la capa de cliente Cliente en Connexus (Parte 7): Añadir la capa de cliente
En este artículo continuamos con el desarrollo de la biblioteca Connexus. En este capítulo creamos la clase CHttpClient, responsable de enviar una solicitud y recibir un orden. También cubrimos el concepto de simulaciones, dejando la biblioteca desacoplada de la función WebRequest, lo que permite una mayor flexibilidad para los usuarios.