Русский Português
preview
Redes neuronales en el trading: Agente multimodal con herramientas complementarias (FinAgent)

Redes neuronales en el trading: Agente multimodal con herramientas complementarias (FinAgent)

MetaTrader 5Sistemas comerciales |
173 1
Dmitriy Gizlyk
Dmitriy Gizlyk

Introducción

Los mercados financieros juegan un papel clave en la estabilidad económica, facilitando la asignación de capital y la gestión del riesgo. Los sistemas comerciales financieros modernos basados en el análisis técnico mejoran estos procesos, pero en mercados muy volátiles y cambiantes, a menudo se enfrentan a una serie de limitaciones. Los sistemas comerciales basados en reglas son rígidos y difíciles de adaptar a la rápida evolución de las condiciones del mercado, lo que a menudo hace que su eficacia disminuya. Los sistemas basados en algoritmos de aprendizaje por refuerzo (RL) muestran una mayor adaptabilidad, pero tienen sus propios inconvenientes:

  • gran demanda de amplios datos de entrenamiento;
  • insuficiente poder explicativo de las soluciones;
  • problemas de generalización a distintas condiciones de mercado;
  • sensibilidad al ruido del mercado;
  • integración limitada de la información de mercado multimodal.

En los últimos años, los grandes modelos lingüísticos (LLM) han mostrado un potencial significativo en la toma de decisiones, ampliando su alcance más allá del procesamiento del lenguaje natural. La integración de módulos de memoria y programación permite a los LLM adaptarse a entornos que cambian dinámicamente. Los LLM multimodales mejoran estas capacidades procesando información textual y visual, mientras que la adición de herramientas externas amplía la gama de tareas que pueden abordar estos modelos, incluidos los escenarios financieros complejos.

A pesar de los avances en el análisis de datos financieros, los agentes LLM se enfrentan a una serie de limitaciones:

  • el procesamiento multimodal limitado de los datos numéricos, textuales y visuales;
  • la necesidad de integrar con precisión los datos procedentes de distintas fuentes;
  • la escasa adaptabilidad a la rápida evolución de los mercados;
  • las dificultades para utilizar los conocimientos especializados y los métodos tradicionales;
  • la falta de transparencia en la toma de decisiones.

Los autores del artículo "A Multimodal Foundation Agent for Financial Trading: Tool-Augmented, Diversified, and Generalist", presentan el framework FinAgent, un agente básico multimodal que combina información textual y visual para analizar la dinámica del mercado y los datos históricos. Los componentes básicos de FinAgent incluyen el procesamiento multimodal de datos para identificar las tendencias clave del mercado, un módulo de reflexión en dos niveles que analiza las decisiones a corto y largo plazo, un sistema de memoria para minimizar el ruido en los resultados de los análisis y un módulo de toma de decisiones que integra conocimientos expertos y estrategias comerciales avanzadas.


El algoritmo FinAgent

El framework FinAgent es una herramienta para el análisis de datos y la toma de decisiones informadas en los mercados financieros. Ofrece a los usuarios un conjunto de herramientas para comprender los procesos del mercado, prever su dinámica y optimizar las estrategias comerciales. FinAgent incluye cinco módulos básicos que interactúan para crear un ecosistema de procesamiento de datos y toma de decisiones.

El módulo de análisis de mercado se encarga de recopilar, procesar e interpretar diversos datos, como noticias bursátiles, variaciones de precios e informes de compañías. Usando técnicas de vanguardia, el módulo identifica patrones ocultos, lo cual permite adaptar las acciones del agente a las condiciones actuales del mercado.

Para lograr la máxima eficacia, FinAgent analiza los datos actuales del mercado y la información histórica acumulada. Por ejemplo, las actualizaciones diarias, como noticias, cambios de precios y otros datos operativos, constituyen la base de las decisiones a corto plazo. Al mismo tiempo, el análisis de eventos pasados ayuda a identificar patrones a largo plazo, de modo que puedan desarrollarse estrategias sostenibles para el futuro. Esta combinación de ambos enfoques aporta al sistema una gran adaptabilidad y flexibilidad.

El proceso de recuperación de datos de FinAgent se basa en el uso de un gran modelo de lenguaje (LLM) que convierte la información de mercado en consultas de texto. A continuación, estas consultas se usan para buscar datos similares en la base de datos histórica del módulo de memoria. El uso de técnicas de similitud vectorial mejora la precisión de búsqueda y ayuda a centrarse en la información más relevante. Además, el sistema usa campos de texto especializados, lo que mejora el procesamiento de los datos y evita la pérdida de detalles importantes. Los datos resultantes se estructuran y resumen para simplificar el proceso de análisis y minimizar la influencia de la información no esencial.

Un bloque con dos módulos de reflexión de distintos niveles cumple funciones similares al proceso de aprendizaje humano. El módulo de reflexión de bajo nivel ayuda a identificar las correlaciones entre las variaciones de precios y la dinámica del mercado. Esto permite predecir las fluctuaciones del mercado a corto plazo, lo cual resulta especialmente importante para los tráders que trabajan con intervalos de tiempo pequeños. Al mismo tiempo, el módulo de reflexión de alto nivel analiza relaciones más complejas y profundas basándose en datos históricos y en los resultados de las decisiones comerciales anteriores. Esto ayuda a identificar errores y a desarrollar métodos para corregirlos. Dicho proceso implica visualizar los puntos clave, como los momentos de compra y venta, y evaluar su eficacia. El enfoque de aprendizaje iterativo da al agente la oportunidad de acumular experiencia y utilizarla para mejorar sus acciones futuras.

El módulo de memoria desempeña un papel fundamental para garantizar el funcionamiento estable de FinAgent. Se encarga de almacenar datos y recuperarlos de manera eficiente. El uso de técnicas de búsqueda vectorial permite encontrar rápidamente información relevante en enormes cantidades de datos, disminuyendo el ruido y aumentando la precisión de los análisis. El mecanismo de memoria de FinAgent desempeña un papel fundamental a la hora de ofrecer contexto y capacidades cognitivas al sistema. En el comercio financiero, la memoria resulta especialmente importante porque ofrece precisión, adaptabilidad y capacidad para aprender de experiencias pasadas. Esto permite al agente usar las últimas noticias e informes para predecir futuros cambios del mercado, adaptarse a condiciones volátiles y mejorar continuamente sus estrategias.

El módulo de decisión integra y procesa los datos clave, incluidos resúmenes de datos de mercado, análisis de la dinámica de precios basados en perspectivas de bajo nivel y perspectivas de análisis de decisiones anteriores. También incluye herramientas que se complementan con asesoramiento profesional en materia de inversión y estrategias comerciales probadas en el tiempo. Una parte importante del módulo consiste en analizar el sentimiento del mercado, predecir tendencias alcistas y bajistas basándose en los movimientos actuales de los precios y reflexionar sobre las lecciones asimiladas. Además, el módulo tiene en cuenta las directrices profesionales y valora la eficacia de los indicadores tradicionales.

Usando como base una combinación de estos análisis y considerando la situación financiera actual, se toma la decisión final de comprar, vender o mantener el activo. Los principios del aprendizaje contextualizado se usan para crear una estructura lógica para las decisiones. Todo ello permite dar a cada operación una justificación, garantizando que cada acción se base en una comprensión plena de la dinámica del mercado de manera sensible al contexto. Este planteamiento nos ayuda a adaptarnos mejor a las condiciones del mercado y a tomar decisiones estratégicas fundamentadas.

FinAgent es, por consiguiente, una herramienta integral que combina el análisis de datos, la reflexión y la automatización de procesos. Esto permite a los tráders y analistas adaptarse eficazmente al mercado, minimizar los riesgos y aumentar la rentabilidad, descubriendo nuevas oportunidades para la planificación estratégica.

A continuación le mostramos la visualización del framework FinAgent realizada por el autor.

Visualización del framework FinAgent por parte del autor


Implementación con MQL5

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

Aquí resulta importante señalar que, al igual que en artículos anteriores, excluiremos el uso de grandes modelos lingüísticos e intentaremos aplicar los enfoques propuestos con las herramientas de que disponemos.

Y empezaremos nuestro trabajo creando los módulos de reflexión de bajo y alto nivel.

Módulo de reflexión de bajo nivel

Y al empezar a trabajar en la construcción del módulo de reflexión de bajo nivel, me gustaría llamar su atención sobre la arquitectura del módulo de memoria propuesto por los autores del framework FinAgent. La cuestión es que el módulo de memoria puede dividirse condicionalmente en 3 objetos que recopilan información del módulo de análisis de mercado y dos módulos de reflexión de distintos niveles. Esto nos permite reformatear la estructura de los módulos del modelo sin cambiar el flujo global de información e incorporar bloques de memoria individuales a sus respectivos módulos. Aprovechando esta propiedad, integraremos un bloque de memoria de este flujo de información en el módulo de reflexión de bajo nivel.

Implementaremos el módulo de reflexión de bajo nivel modificado dentro del objeto CNeuronLowLevelReflection, cuya estructura se muestra a continuación.

class CNeuronLowLevelReflection :   public CNeuronMemory
  {
protected:
   CNeuronLSTMOCL    cChangeLSTM;
   CNeuronMambaOCL   cChangeMamba;
   CNeuronRelativeCrossAttention cCrossAttention[2];
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronLowLevelReflection(void) {};
                    ~CNeuronLowLevelReflection(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key,
                          uint units_count, uint heads,
                          ENUM_OPTIMIZATION optimization_type, uint batch) override;
   //---
   virtual int       Type(void) override   const   {  return defNeuronLowLevelReflection; }
   //---
   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;
  };

El uso de un objeto de memoria como clase madre ofrece la oportunidad de integrar a la perfección los procesos de análisis de la información de mercado con las funciones cognitivas de la memoria en una única línea troncal de información. Esto permite al sistema no solo procesar los datos de origen con eficacia, sino también incorporar información histórica, lo cual aporta contexto a los análisis actuales.

En la estructura de clases, incluiremos objetos recurrentes de diferentes arquitecturas y bloques de atención cruzada. Estos elementos desempeñarán un papel importante en el procesamiento de la información y la toma de decisiones. La finalidad y la funcionalidad de estos componentes se detallarán conforme apliquemos los métodos de nuestro nuevo objeto, lo que permitirá una comprensión más detallada de su impacto en el funcionamiento del sistema.

Todos los objetos internos se declararán estáticamente, lo que nos permitirá dejar vacíos el constructor y el destructor de la clase, mientras que la inicialización de todos los objetos declarados y heredados se realizará en el método Init.

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

Tenga en cuenta que la estructura de parámetros de este método se heredará al completo de la clase padre. Y en el cuerpo del método llamaremos inmediatamente al método homónimo de la clase padre, pasándole todos los parámetros recibidos.

Permítame recordarle que el método de la clase padre ya implementa el algoritmo de control de los parámetros obtenidos y la inicialización de los objetos heredados.

A continuación, inicializaremos los objetos recién declarados. En primer lugar, inicializaremos dos objetos recurrentes diseñados para revelar la dinámica de los parámetros analizados, mientras que el uso de diferentes arquitecturas para estos objetos posibilitará un análisis más profundo, permitiendo que el sistema se adapte eficazmente a los cambios tanto a corto como a largo plazo mediante la identificación de tendencias en diferentes escalas temporales.

   int index = 0;
   if(!cChangeLSTM.Init(0, index, OpenCL, window, units_count, optimization, iBatch))
      return false;
   index++;
   if(!cChangeMamba.Init(0, index, OpenCL, window, 2 * window, units_count, optimization, iBatch))
      return false;

Los resultados del análisis se integrarán en una única solución mediante bloques de atención cruzada, que permitirán combinar eficazmente información de distintas fuentes y niveles, centrándose en las relaciones y dependencias clave entre parámetros. La atención cruzada facilitará la identificación de patrones y relaciones ocultas, lo que mejorará la toma de decisiones ofreciendo una percepción más precisa y coherente de la información.

   for(int i = 0; i < 2; i++)
     {
      index++;
      if(!cCrossAttention[i].Init(0, index, OpenCL, window, window_key, units_count, heads,
                                                window, units_count, optimization, iBatch))
         return false;
     }
//---
   return true;
  }

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

La siguiente etapa de nuestro trabajo consistirá en construir los algoritmos de pasada directa para el módulo de reflexión de bajo nivel en el marco del método feedForward.

bool CNeuronLowLevelReflection::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cChangeLSTM.FeedForward(NeuronOCL))
      return false;
   if(!cChangeMamba.FeedForward(NeuronOCL))
      return false;

En los parámetros del método obtendremos el puntero al objeto de datos de origen, que contendrá una descripción del estado actual del entorno. Estos datos se introducirán en los métodos homónimos de nuestros objetos recurrentes para revelar la dinámica de las métricas, lo que nos permitirá controlar los cambios en el entorno y adaptar nuestras decisiones en respuesta a estos cambios.

A continuación, utilizaremos bloques de atención cruzada para enriquecer la descripción del estado actual del entorno con la información sobre los cambios identificados. Esto permitirá integrar los nuevos datos con los ya existentes, reforzando el contexto y mejorando la percepción de la dinámica del entorno. Este enfoque creará una especie de "rastro" de la trayectoria de movimiento que representará los cambios en el estado analizado.

   if(!cCrossAttention[0].FeedForward(NeuronOCL, cChangeLSTM.getOutput()))
      return false;
   if(!cCrossAttention[1].FeedForward(cCrossAttention[0].AsObject(), cChangeMamba.getOutput()))
      return false;

Los resultados del análisis se pasarán al módulo de memoria, cuyo papel desempeñará la clase padre. Este módulo permite identificar tendencias estables, sobre cuya base se formará la tendencia del próximo movimiento de precios.

   return CNeuronMemory::feedForward(cCrossAttention[1].AsObject());
  }

Como podemos ver, dentro del algoritmo de pasada directa, los datos de entrada de la descripción del estado del entorno analizado han sido utilizados por los tres objetos internos. Y como ya habrá adivinado, al organizar los procesos de pasada inversa, tendremos que recoger los gradientes de error de los tres flujos de información. El algoritmo para distribuir los gradientes de error se implementará en el método calcInputGradients.

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

En los parámetros del método, obtendremos el puntero al mismo objeto de datos fuente. Esta vez, sin embargo, tendremos que transmitir un gradiente de error que refleje el efecto de los datos de entrada en la salida del modelo. En el cuerpo del método comprobaremos inmediatamente la relevancia del puntero recibido, porque de lo contrario no será posible la transferencia de datos.

A continuación, usando la clase padre, haremos descender el gradiente de error a través del módulo de memoria hasta el nivel de los bloques de atención cruzada.

   if(!CNeuronMemory::calcInputGradients(cCrossAttention[1].AsObject()))
      return false;
   if(!cCrossAttention[0].calcHiddenGradients(cCrossAttention[1].AsObject(),
                       cChangeMamba.getOutput(), cChangeMamba.getGradient(), 
                                (ENUM_ACTIVATION)cChangeMamba.Activation()))
      return false;

Y distribuiremos el error obtenido entre los bloques de recurrencia.

Luego deberemos reducir el gradiente de error de los tres flujos de información hasta el nivel de los datos de origen. Para ello, primero pasaremos el gradiente de error por la línea troncal de los bloques de atención cruzada.

   if(!NeuronOCL.calcHiddenGradients(cCrossAttention[0].AsObject(), cChangeLSTM.getOutput(),
                      cChangeLSTM.getGradient(), (ENUM_ACTIVATION)cChangeLSTM.Activation()))
      return false;

Y a continuación, sustituiremos el puntero al búfer de gradientes de error del objeto de datos de origen y enviaremos la información sobre el error por las líneas troncales de los objetos recurrentes, con la consiguiente suma de los valores obtenidos en diferentes flujos de información.

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

Después de eso, los punteros a los búferes de datos deberán volver a su estado original, lo que garantizará que los datos estén debidamente preparados para su uso posterior.

   if(!NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

Y al final del método, pasaremos el resultado lógico de las operaciones al programa que realiza la llamada.

Aquí concluiremos nuestro análisis de los algoritmos para construir los métodos del módulo de reflexión de bajo nivel. Podrá leer el código completo de esta clase y todos sus métodos por sí mismo en el archivo adjunto.

Módulo de reflexión de alto nivel

La próxima etapa de nuestro trabajo será la creación de un módulo de reflexión de alto nivel que se centrará en un análisis más profundo y complejo de las interrelaciones. A diferencia del módulo de reflexión de bajo nivel, que se centra en los cambios a corto plazo y la dinámica del estado actual, el módulo de reflexión de alto nivel explorará las tendencias a largo plazo y las relaciones identificadas durante operaciones comerciales anteriores.

La principal tarea de este módulo consistirá en evaluar a fondo la validez de las decisiones tomadas previamente, así como analizar los resultados reales obtenidos. Este proceso ayudará a determinar hasta qué punto se ha aplicado eficazmente la estrategia comercial actual y si resultará adecuada para los fines perseguidos. Una parte importante del análisis realizado consistirá en identificar los puntos fuertes y débiles de la estrategia.

Además, el módulo de reflexión de alto nivel deberá ofrecer recomendaciones específicas para optimizar la estrategia comerciales. Estas recomendaciones tendrán por objeto aumentar la rentabilidad de las operaciones comerciales y/o reducir los riesgos.

Y aquí se hace evidente que necesitaremos muchos más datos iniciales para realizar plenamente la funcionalidad del módulo de reflexión de alto nivel. En primer lugar, necesitaremos analizar las acciones reales del agente.

Asimismo, necesitaremos una descripción del estado del entorno en el momento de la toma de decisiones. Esto nos permitirá evaluar si una decisión concreta era razonable en condiciones de mercado específicas y cómo esas condiciones pueden haber influido en el resultado.

Un elemento importante será la información sobre pérdidas y ganancias, que nos ayudará a evaluar cómo las decisiones tomadas han afectado al resultado financiero. Esta información permitirá no solo evaluar el éxito de las operaciones comerciales, sino también detectar posibles puntos débiles de la estrategia que requieran ajustes.

Además, el funcionamiento eficaz del módulo de reflexión de alto nivel requerirá que los resultados del análisis se almacenen para su uso futuro. De este modo, el sistema podrá incorporar los resultados del pasado y mejorar sus decisiones basándose en las lecciones aprendidas. Los autores del framework FinAgent previeron esta posibilidad e implementaron en la arquitectura el correspondiente bloque de módulos de memoria.

Así, tendremos cuatro flujos de información de origen:

  • Las acciones del agente;
  • El estado del entorno;
  • El resultado financiero (estado de la cuenta);
  • La memoria.

Recordemos que en la implementación actual de nuestras interfaces para el intercambio de datos entre objetos del modelo, solo se permiten dos flujos de información.

De forma similar al módulo de reflexión de bajo nivel, al implementar el módulo de reflexión de alto nivel, utilizaremos el módulo de memoria como clase padre del nuevo objeto. Esta solución permitirá que el flujo de memoria se genere directamente dentro del objeto, eliminando la necesidad de fuentes adicionales de datos de origen. De este modo, el módulo se volverá más autónomo y será capaz de procesar y almacenar datos de forma eficiente en su propia estructura.

Además, los resultados del módulo de reflexión de alto nivel podrán interpretarse con alta probabilidad como una representación latente del tensor de acciones del agente. Esto se debe a que las acciones del agente suponen esencialmente una función de los resultados proporcionados por nuestro bloque. Por consiguiente, los cambios en los resultados de los análisis realizados por el módulo afectarán directamente al ajuste de las acciones del agente. Este enfoque permitirá la formación de un modelo de comportamiento más adaptativo, en el que las acciones del agente se optimizarán dinámicamente a partir de la información procedente de la reflexión de alto nivel.

Así, utilizando los supuestos y las decisiones arquitectónicas anteriores, construiremos un modelo básico de interfaces de un nuevo objeto que trabajará con dos fuentes de datos de entrada. Estas proporcionarán la información necesaria para analizar las condiciones actuales y los resultados de acciones anteriores.

Sin embargo, hay otro aspecto importante a considerar. Para analizar la política de comportamiento del agente en su conjunto, en lugar de centrarse únicamente en operaciones individuales, los autores del framework FinAgent sugieren incluir el análisis de gráficos de balance con etiquetas de operaciones comerciales. Este enfoque ofrecerá una visión generalizada del rendimiento de toda la estrategia del agente. En nuestra implementación, queremos replicar dicha solución añadiendo objetos recurrentes para procesar los tensores de descripción del estado de la cuenta y las acciones de agentes. Estas facilidades permitirán modelizar la relación entre la dinámica del balance y las acciones comerciales, lo cual posibilitará un análisis más profundo de las políticas utilizadas.

Las soluciones descritas anteriormente las implementaremos dentro del nuevo objeto CNeuronHighLevelReflection, cuya estructura presentamos a continuación.

class CNeuronHighLevelReflection :  public CNeuronMemory
  {
protected:
   CNeuronBaseOCL    cAccount;
   CNeuronLSTMOCL    cHistoryAccount;
   CNeuronRelativeCrossAttention cActionReason;
   CNeuronLSTMOCL    cHistoryActions;
   CNeuronRelativeCrossAttention cActionResult;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
                                        CBufferFloat *SecondGradient, 
                                        ENUM_ACTIVATION SecondActivation = None) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override {return false; }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, 
                                        CBufferFloat *SecondInput) override;

public:
                     CNeuronHighLevelReflection(void) {};
                    ~CNeuronHighLevelReflection(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count, uint heads,
                          uint desc_account, uint actions_state,
                          ENUM_OPTIMIZATION optimization_type, uint batch) override;
   //---
   virtual int       Type(void) override   const   {  return defNeuronHighLevelReflection; }
   //---
   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;
  }; 

La estructura del objeto contendrá un conjunto familiar de métodos redefinibles que ofrecerán flexibilidad y adaptabilidad en la implementación de la funcionalidad sin violar la arquitectura general del sistema. Además, la nueva clase contendrá varios objetos internos cuya funcionalidad se discutirá con detalle durante la construcción de los algoritmos para los métodos anteriores.

Todos los objetos internos se declararán estáticamente, lo que nos permitirá dejar vacíos el constructor y el destructor de la clase, La inicialización de todos los objetos declarados y heredados se realizará en el método Init.

bool CNeuronHighLevelReflection::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                      uint window, uint window_key, uint units_count,
                                      uint heads, uint desc_account, uint actions_state,
                                      ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMemory::Init(numOutputs, myIndex, open_cl, 3, window_key, actions_state / 3,
                           heads, optimization_type, batch))
      return false;

En los parámetros del método de inicialización obtendremos un conjunto de constantes que nos permitirán definir inequívocamente la arquitectura del objeto a crear. Y uno de estos parámetros indicará la dimensionalidad del vector de acciones del agente.

A la salida de este objeto, como hemos mencionado antes, esperamos obtener alguna representación latente del tensor de acciones del agente. Cada operación comercial de nuestro agente se caracterizará por tres parámetros: el volumen de la operación y los niveles comerciales (stop loss y take profit). Las operaciones de compra y venta estarán representadas por diferentes líneas del tensor, lo que permitirá renunciar al parámetro adicional de la dirección de la operación comercial.

En el cuerpo del método, siguiendo la práctica establecida, llamaremos al método homónimo de la clase padre (en este caso, el módulo de memoria). Los datos del tensor de acciones del agente, estructurados según los supuestos descritos anteriormente, se transmitirán a los parámetros de llamada. Este enfoque permite mantener la continuidad de la funcionalidad y utilizar las capacidades básicas de la clase padre para el procesamiento de datos. Esto permitirá integrar los resultados del análisis realizado en el objeto actual en la estructura global del modelo, garantizando su coherencia y accesibilidad para los objetos posteriores.

Tras ejecutar con éxito las operaciones del método de la clase padre, inicializaremos los objetos anidados. Aquí primero inicializaremos la capa básica completamente conectada, que utilizaremos para ofrecer correctamente servicio a los datos del segundo flujo de información.

   int index = 0;
   if(!cAccount.Init(0, index, OpenCL, desc_account, optimization, iBatch))
      return false;

Y luego inicializaremos un bloque de recurrencia para rastrear los cambios en el estado de la cuenta.

   index++;
   if(!cHistoryAccount.Init(0, index, OpenCL, desc_account, 1, optimization, iBatch))
      return false;

Para analizar la validez de la última decisión comercial, usaremos el bloque de atención cruzada, en el que analizaremos las dependencias entre las acciones del agente y la descripción tensorial del estado del entorno.

   index++;
   if(!cActionReason.Init(0, index, OpenCL, iWindow, iWindowKey, iUnits, iHeads,
                          window, units_count, optimization, iBatch))
      return false;

La dinámica de las acciones la recogeremos en el bloque de recurrencia correspondiente.

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

Después, evaluaremos la eficacia de la política de comportamiento del agente comparando la dinámica de las operaciones comerciales y el estado de la cuenta en el bloque de atención cruzada.

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

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

A continuación procederemos a construir el algoritmo de pasada directa para nuestro módulo de reflexión de alto nivel dentro del método feedForward.

bool CNeuronHighLevelReflection::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!NeuronOCL || !SecondInput)
      return false;

En los parámetros del método obtendremos los punteros a los objetos de datos de origen de los dos flujos de información. Y comprobaremos inmediatamente la pertinencia de los punteros recibidos.

Aquí cabe señalar que el segundo flujo de información estará representado por el búfer de datos. Y antes de iniciar otras operaciones, utilizaremos el puntero obtenido para sustituir el búfer de resultados de un objeto interno especialmente preparado. Esto permitirá usar las interfaces básicas de los objetos internos para procesar los datos recibidos.

   if(cAccount.getOutput() != SecondInput)
     {
      if(cAccount.Neurons() != SecondInput.Total())
         if(!cAccount.Init(0, 0, OpenCL, SecondInput.Total(), optimization, iBatch))
            return false;
      if(!cAccount.SetOutput(SecondInput, true))
         return false;
     }

A continuación, evaluaremos el cambio en el estado de la cuenta usando un bloque de recurrencia.

   if(!cHistoryAccount.FeedForward(cAccount.AsObject()))
      return false;

Y comprobaremos la validez de la última decisión comercial comparándola con el estado actual del entorno mediante el bloque de atención cruzada.

   if(!cActionReason.FeedForward(this.AsObject(), NeuronOCL.getOutput()))
      return false;

Aquí debemos señalar la distinción entre el tensor que describe el estado actual del entorno y el que se utiliza para tomar una decisión comercial. El tensor de estado del entorno llegará a la entrada del modelo ya después de que se haya realizado la acción y el sistema haya pasado a un nuevo estado. Sin embargo, no se espera que esta diferencia tenga un impacto sustancial en los resultados del análisis.

Se supone que se iniciará una nueva iteración del modelo cada vez que se forme una nueva barra. Al mismo tiempo, la profundidad de la historia analizada superará con creces una barra, lo que ofrecerá un alto grado de detalle y exhaustividad al análisis. Como resultado de la transición al nuevo estado, se producirá un desplazamiento de los datos en un elemento de la serie temporal multimodal analizada, quedando excluida del análisis la barra más alejada. Se espera que esta barra tenga un impacto mínimo en la acción actual y, por consiguiente, su pérdida tendrá poco o ningún efecto en la precisión del análisis. Esto le permitirá evaluar con una confianza razonable la validez de una decisión comercial tomada previamente.

Además, la adición de una nueva barra que no se conocía al momento de tomar la decisión, proporcionará información adicional para evaluar si la operación comercial es correcta. Este mecanismo ayudará a identificar no solo las consecuencias de una decisión comercial, sino también la conformidad entre los cambios reales en las condiciones del mercado y las previsiones anteriores.

Los resultados del análisis se pasarán al bloque de recurrencia de seguimiento de la política de comportamiento del agente.

   if(!cHistoryActions.FeedForward(cActionReason.AsObject()))
      return false;

A continuación, analizaremos la política de comportamiento en el contexto del resultado financiero usando el bloque de atención cruzada.

   if(!cActionResult.FeedForward(cHistoryActions.AsObject(), cHistoryAccount.getOutput()))
      return false;

Luego sustituiremos el búfer de resultados para guardar las últimas acciones del agente y organizar el proceso correcto de pasada inversa, llamando a continuación al método homónimo de la clase padre, que realizará las funciones del módulo de memoria.

   if(!SwapBuffers(Output, PrevOutput))
      return false;
//---
   return CNeuronMemory::feedForward(cActionResult.AsObject());
  }

Después retornaremos el resultado lógico de las operaciones al programa que realiza la llamada y finalizaremos el método.

Una vez finalizado el trabajo de construcción del algoritmo del método de pasada directa, organizaremos los procesos de pasada inversa. En este caso, se utilizarán algoritmos lineales que no plantean grandes dificultades de aplicación. Por lo tanto, no nos detendremos a analizarlos con detalle. Podrá comprobarlo por sí mismo en el archivo adjunto, que muestra el código completo del objeto de reflexión de alto nivel y todos sus métodos.

En esta etapa, hemos agotado el alcance del artículo, pero aún no hemos completado el trabajo de implementación de los enfoques del framework FinAgent. Así que haremos una breve pausa y en el próximo artículo llevaremos la construcción del framework a su conclusión lógica.


Conclusión

En este artículo nos hemos familiarizado con el framework FinAgent, una solución innovadora que integra información textual y visual para analizar exhaustivamente la dinámica del mercado y los datos históricos. Con sus cinco componentes principales, FinAgent ofrece una gran precisión y adaptabilidad en las decisiones comerciales. Esto convierte al framework en una herramienta prometedora para desarrollar estrategias comerciales eficaces y flexibles, capaces de funcionar en un mercado volátil.

En la parte práctica, hemos implementado nuestra visión de los dos módulos de reflexión de distintos niveles usando herramientas MQL5. En el próximo artículo continuaremos el trabajo iniciado, completándolo con la comprobación de la eficacia de las soluciones implementadas usando 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 la 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 de programa OpenCL

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

Archivos adjuntos |
MQL5.zip (2327.7 KB)
Dominic Michael Frehner
Dominic Michael Frehner | 9 ene 2025 en 13:19

Sería estupendo que también escribiera artículos en inglés.

Del básico al intermedio: Indicador (IV) Del básico al intermedio: Indicador (IV)
En este artículo, veremos lo fácil que es crear e implementar una metodología operativa para teñir velas. Este es un concepto muy apreciado por los operadores. Es necesario tener cuidado al implementar este tipo de cosas para que las barras o velas mantengan su apariencia original y no se dificulte la lectura vela por vela.
De lo básico a intermedio: Indicador (III) De lo básico a intermedio: Indicador (III)
En este artículo, veremos cómo declarar diversos indicadores de representación gráfica, como DRAW_COLOR_LINE y DRAW_FILLING. Además, por supuesto, aprenderemos a trazar múltiples indicadores de forma sencilla, práctica y rápida. Esto puede cambiar realmente tu forma de ver MetaTrader 5 y el mercado en general.
Analizamos el código binario de los precios en bolsa (Parte I): Una nueva visión del análisis técnico Analizamos el código binario de los precios en bolsa (Parte I): Una nueva visión del análisis técnico
En este artículo presentaremos un enfoque innovador del análisis técnico basado en la conversión de los movimientos de los precios en código binario. El autor demostrará cómo diversos aspectos del comportamiento de los mercados -desde simples movimientos de precios hasta patrones complejos- pueden codificarse en una secuencia de ceros y unos.
Redes generativas antagónicas (GAN) para datos sintéticos en modelos financieros (Parte 1): Introducción a las GAN y los datos sintéticos en modelos financieros Redes generativas antagónicas (GAN) para datos sintéticos en modelos financieros (Parte 1): Introducción a las GAN y los datos sintéticos en modelos financieros
Este artículo presenta a los operadores bursátiles las redes generativas antagónicas (Generative Adversarial Networks, GAN) para generar datos financieros sintéticos, abordando las limitaciones de datos en el entrenamiento de modelos. Este artículo presenta a los operadores bursátiles las redes generativas antagónicas (GAN) para generar datos financieros sintéticos, abordando las limitaciones de datos en el entrenamiento de modelos.