English Русский Deutsch 日本語 Português
preview
Redes neuronales: así de sencillo (Parte 41): Modelos jerárquicos

Redes neuronales: así de sencillo (Parte 41): Modelos jerárquicos

MetaTrader 5Asesores Expertos | 12 septiembre 2023, 16:08
318 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Introducción

En este artículo, analizaremos la aplicación del aprendizaje por refuerzo jerárquico en el trading. Le proponemos utilizar este enfoque para crear un modelo comercial jerárquico que podrá tomar decisiones óptimas en diferentes niveles y adaptarse a distintas condiciones del mercado.

En este artículo, describiremos la arquitectura del modelo jerárquico, incluidos varios niveles de toma de decisiones, como la determinación de los puntos de entrada y salida del comercio. También presentaremos algunos métodos de aprendizaje de modelo jerárquico que combinan el aprendizaje por refuerzo a nivel global y el aprendizaje por refuerzo a nivel local.

El uso del aprendizaje jerárquico permite modelar estructuras complejas de toma de decisiones, así como usar eficazmente el conocimiento en diferentes niveles. Esto nos ayuda a aumentar la capacidad de generalización del modelo y su adaptabilidad a las condiciones cambiantes del mercado.


1. Ventajas de los modelos jerárquicos

En los últimos años, el uso de modelos jerárquicos en el trading ha atraído cada vez más atención, propiciando multitud de investigaciones. El aprendizaje jerárquico es una técnica poderosa para modelar estructuras jerárquicas complejas para la toma de decisiones. En el contexto del trading, esto puede traer varios beneficios importantes.

La primera ventaja es la capacidad del modelo jerárquico para adaptarse a distintas condiciones del mercado. El modelo puede analizar factores macroeconómicos de nivel superior, como acontecimientos políticos o indicadores económicos, y al mismo tiempo tener en cuenta factores microeconómicos de nivel inferior, como el análisis técnico o la información específica de activos. Esto permite al modelo tomar decisiones más fundamentadas y adaptarse a diferentes situaciones del mercado.

La segunda ventaja se relaciona con un uso más eficiente de la información disponible: los modelos jerárquicos permiten modelar y utilizar información en diferentes niveles de la jerarquía. Las estrategias de alto nivel pueden considerar tanto tendencias generales como patrones, mientras que las estrategias de bajo nivel pueden tener en cuenta datos más precisos y que cambien rápidamente. Esto permite que el modelo obtenga una imagen más completa del mercado y tome decisiones más fundamentadas.

La tercera ventaja de los modelos jerárquicos es su capacidad para asignar recursos informáticos de forma eficiente. Las estrategias de alto nivel se pueden entrenar en una escala de tiempo mayor, mientras que las estrategias de bajo nivel pueden resultar más sensibles a los datos que cambian rápidamente en una escala de tiempo menor. Esto permite un uso eficiente de los recursos informáticos y acelera el proceso de entrenamiento del modelo.

La cuarta ventaja está relacionada con la mejora de la estabilidad y portabilidad de las estrategias. Los modelos jerárquicos tienen un gran poder de generalización porque son capaces de modelar conceptos abstractos y dependencias en diferentes niveles de la jerarquía. Esto nos permite construir estrategias sostenibles que puedan aplicarse con éxito en diversas condiciones y transferirse a diferentes mercados y activos.

La quinta ventaja del uso de modelos jerárquicos es la capacidad de dividir una tarea comercial compleja en subtareas más simples. Esto reduce la complejidad de la tarea, simplificando el proceso de aprendizaje. Cada nivel de la jerarquía puede ser responsable de ciertos aspectos comerciales, como la determinación de los puntos de entrada y salida de la negociación, la gestión de riesgos o la distribución del portafolio. Esto posibilita una formación más eficiente del modelo y mejora la calidad de sus soluciones.

Finalmente, el uso de modelos jerárquicos contribuye a una mejor interpretabilidad de los resultados y decisiones tomadas. Como el modelo tiene una estructura jerárquica clara, es más fácil comprender qué factores y variables influyen en la toma de decisiones en cada nivel. Esto permite a los tráders e investigadores comprender mejor las causas y efectos de sus estrategias y realizar los ajustes necesarios.

Por ello, el uso de modelos jerárquicos en problemas comerciales ofrece una serie de ventajas, incluida la adaptabilidad a las condiciones del mercado, el uso eficiente de la información, la distribución de recursos informáticos, la estabilidad y portabilidad de las estrategias, la división de tareas complejas en subtareas y una mejor interpretabilidad de resultados. Estas ventajas hacen de los modelos jerárquicos una potente herramienta para desarrollar estrategias comerciales exitosas. 

El uso de modelos jerárquicos en el trading requiere enfoques especiales de aprendizaje. Los métodos de aprendizaje tradicionales aplicados en modelos de un solo nivel no siempre resultan adecuados para modelos jerárquicos debido a su estructura compleja y las relaciones entre los niveles de esta.

El uso del aprendizaje jerárquico es uno de los enfoques específicos para entrenar estos modelos. Al mismo tiempo, el modelo se entrena con etapas en diferentes niveles de la jerarquía, comenzando desde los niveles inferiores y pasando sucesivamente a los superiores. A medida que se entrena con cada nivel, el modelo usa la información aprendida en niveles anteriores, lo cual le permite captar dependencias y conceptos más abstractos en los niveles superiores de la jerarquía.

Además, un enfoque importante consiste en combinar el aprendizaje por refuerzo y el aprendizaje supervisado. En este caso, el modelo se entrena según la recompensa obtenida durante la realización de la tarea con refuerzo, y también utilizando los ejemplos de entrenamiento ofrecidos en cada nivel de la jerarquía. Este enfoque permite que el modelo aprenda de la experiencia de otros agentes y use conocimientos ya conocidos en niveles superiores de la jerarquía.

Asimismo, un aspecto importante del aprendizaje de modelos jerárquicos es su capacidad para adaptarse a condiciones cambiantes. El modelo debe ser flexible y capaz de adaptarse rápidamente a las nuevas condiciones del mercado y a los cambios ocurridos en los datos. Para ello podemos utilizar el aprendizaje dinámico, que incluye la regularización periódica del modelo y la actualización de sus parámetros según los nuevos datos.

Uno de los ejemplos claros de algoritmos para aprender modelos jerárquicos en el trading es Scheduled Auxiliary Control (SAC-X).

El algoritmo Scheduled Auxiliary Control (SAC-X) es un método de aprendizaje por refuerzo que utiliza una estructura jerárquica para la toma de decisiones, y representa un nuevo enfoque hacia la resolución de problemas con recompensas escasas. Se basa en cuatro principios fundamentales:

  1. Cada par estado-acción va acompañado de un vector de recompensa que consta de recompensas externas (normalmente dispersas) y recompensas auxiliares internas (normalmente dispersas).
  2. A cada entrada de recompensa se le asigna una política llamada intención, que estará entrenada para maximizar la recompensa acumulada correspondiente.
  3. Existe un planificador de alto nivel que selecciona y ejecuta intenciones individuales para mejorar el rendimiento del agente de tareas externo.
  4. El entrenamiento tiene lugar fuera de la política (de forma asincrónica respecto a la ejecución de la política), mientras que la experiencia se intercambia entre intenciones, para el uso eficaz de la información.

El algoritmo SAC-X usa estos principios para resolver eficientemente problemas de recompensa dispersa (escasa). Los vectores de recompensa le permiten aprender de diferentes aspectos de una tarea y crear múltiples intenciones, cada una de las cuales maximiza su recompensa. El planificador gestiona la ejecución de las intenciones seleccionando la estrategia óptima para lograr los objetivos externos. El entrenamiento tiene lugar fuera de la política, lo cual permite el uso de la experiencia con diferentes intenciones para un aprendizaje efectivo.

Este enfoque permite al agente resolver eficientemente problemas de recompensas dispersas aprendiendo de las recompensas externas e internas, mientras que el uso de un planificador permite la coordinación de las acciones. También implica el intercambio de experiencias entre intenciones, lo que posibilita el uso eficiente de la información y mejora el rendimiento general del agente.

SAC-X ofrece un entrenamiento de agentes más eficiente y flexible en entornos de recompensa dispersa. Una característica clave de SAC-X es el uso de recompensas internas auxiliares que ayudan a superar el problema de la escasez de recompensas y facilitan el entrenamiento en tareas de recompensa baja.

Durante el aprendizaje de SAC-X, cada intención tiene su propia política que maximiza la recompensa auxiliar correspondiente. El planificador determina qué intenciones se seleccionarán y ejecutarán en cada momento. Esto permite al agente aprender de varios aspectos de la tarea y usar la información disponible de forma eficiente para lograr resultados óptimos.

Una de las ventajas clave de SAC-X es su capacidad para gestionar toda una variedad de tareas externas. El algoritmo se puede configurar para trabajar con diferentes funciones objetivo y adaptarse a diferentes entornos y tareas. Esto hace que SAC-X resulte adecuado para una amplia gama de aplicaciones.

Además, el intercambio asincrónico de experiencias entre intenciones posibilita el uso eficiente de la información. Un agente puede aprender de intenciones exitosas y usar el conocimiento adquirido para mejorar su rendimiento. Esto permite al agente encontrar estrategias óptimas para resolver problemas complejos de forma más rápida y eficaz.

En general, el algoritmo Scheduled Auxiliary Control (SAC-X) supone un enfoque innovador para entrenar agentes en entornos de recompensa dispersa. Combina el uso de recompensas auxiliares externas e internas, un planificador y aprendizaje asincrónico para lograr un alto rendimiento y adaptabilidad del agente. SAC-X ofrece nuevas oportunidades para resolver problemas complejos y se puede aplicar a una variedad de aplicaciones donde la recompensa dispersa supone un desafío.

El algoritmo de trabajo de SAC-X se puede describir de la siguiente manera:

  1. Inicialización: comienza inicializando las políticas para cada intención y sus respectivos vectores de recompensa. También inicializa el planificador, que seleccionará y ejecutará las intenciones.
  2. Ciclo de aprendizaje:
    1. Recopilación de experiencia: El agente interactúa con el entorno realizando acciones basadas en la intención seleccionada. Luego recopila la experiencia en forma de estados, acciones, recompensas externas recibidas y recompensas auxiliares internas.
    2. Actualización de la intención: para cada intención, la política correspondiente se actualiza usando la experiencia recopilada. La política se ajusta para maximizar la recompensa acumulativa asignada con esa intención.
    3. Planificación: El planificador elige qué intención se ejecutará en el siguiente paso según el estado actual y las intenciones ejecutadas anteriormente. El propósito del planificador es mejorar el rendimiento general del agente en tareas externas.
    4. Aprendizaje asincrónico: Las actualizaciones de las políticas y el planificador se realizan de forma asincrónica, lo cual permite al agente utilizar de forma eficiente la información y la experiencia obtenidas de otras intenciones.
  3. Finalización: El algoritmo continúa el ciclo de entrenamiento hasta que se alcance un criterio de parada determinado, como un rendimiento concreto o un número de iteraciones definido.

El algoritmo SAC-X permite al agente usar eficazmente recompensas auxiliares internas y externas para el entrenamiento y elegir las mejores intenciones para lograr resultados óptimos en tareas externas. Esto permite superar el problema de la escasez de recompensas y mejorar el rendimiento de los agentes en entornos de baja recompensa.


2. Implementación usando MQL5

El algoritmo Scheduled Auxiliary Control (SAC-X) posibilita el entrenamiento asincrónico de agentes con la capacidad de intercambiar libremente la experiencia entre diferentes agentes. Para organizar este proceso, como en el artículo anterior, dividiremos todo el proceso de aprendizaje en 2 etapas:

  • Recolección de experiencia
  • Políticas de entrenamiento (estrategias de comportamiento de los agentes)

Para recopilar experiencia, primero crearemos 2 estructuras. En la primera estructura SSstate, escribiremos la descripción de un estado particular del sistema. Contendrá solo un array estático para almacenar valores de punto flotante.

struct SState
  {
   float             state[HistoryBars * 12 + 9];
   //---
                     SState(void);
   //---
   bool              Save(int file_handle);
   bool              Load(int file_handle);
   //--- overloading
   void              operator=(const SState &obj)   { ArrayCopy(state, obj.state); }
  };

Para facilitar su uso, crearemos en la estructura métodos para trabajar con los archivos Save y Load. El código del método es bastante simple, podrá verlo por su cuenta en el archivo adjunto.

La segunda estructura STrajectory contendrá toda la información sobre la experiencia acumulada del agente en una pasada del episodio. Contiene 3 arrays estáticos:

  • States — array de estados. Este es un array con las estructuras creadas anteriormente, en el que se escribirán todos los estados visitados por el agente.
  • Actions — array de acciones realizadas por el agente.
  • Revards — array de recompensas recibidas del entorno externo.

Además, añadiremos 3 variables:

  • Total — número de estados visitados
  • DiscountFactor — factor de descuento
  • CumCounted — bandera que indica que el recálculo de la recompensa acumulada se realiza teniendo en cuenta el factor de descuento.

struct STrajectory
  {
   SState            States[Buffer_Size];
   int               Actions[Buffer_Size];
   float             Revards[Buffer_Size];
   int               Total;
   float             DiscountFactor;
   bool              CumCounted;
   //---
                     STrajectory(void);
   //---
   bool              Add(SState &state, int action, float reward);
   void              CumRevards(void);
   //---
   bool              Save(int file_handle);
   bool              Load(int file_handle);
  };

A diferencia de la estructura anterior para describir un estado aparte, crearemos un constructor para dicha estructura. En él, inicializaremos los arrays y variables con los valores iniciales.

STrajectory::STrajectory(void)  :   Total(0),
                                    DiscountFactor(0.99f),
                                    CumCounted(false)
  {
   ArrayInitialize(Actions, -1);
   ArrayInitialize(Revards, 0);
  }

Tenga en cuenta que en el constructor configuraremos el número total de estados visitados en "0", y la bandera para calcular la recompensa acumulada CumCounted está en false. Calcularemos directamente la recompensa acumulativa antes de guardar los datos en un archivo pues necesitaremos estos valores al entrenar el modelo.

Usando el método Add, añadiremos conjuntos de estado-acción-recompensa a la base de datos.

bool STrajectory::Add(SState &state, int action, float reward)
  {
   if(Total + 1 >= ArraySize(Actions))
      return false;
   States[Total] = state;
   Actions[Total] = action;
   if(Total > 0)
      Revards[Total - 1] = reward;
   Total++;
//---
   return true;
  }

Tenga en cuenta que guardaremos la recompensa para el estado anterior, ya que se ha obtenido para la transición del estado anterior al actual al realizar la acción seleccionada por el agente en el estado anterior. De esta forma, mantendremos una relación causal entre acción y recompensa.

El método de cálculo de la recompensa acumulada de CumRevards es bastante sencillo, pero deberemos prestar atención al control de la bandera de cálculo completado CumCounted. Este es un punto muy importante, pues este control evita el recálculo de la recompensa acumulada, lo cual puede distorsionar fundamentalmente los datos de la muestra de entrenamiento, y, como resultado, el entrenamiento del modelo en su conjunto.

void STrajectory::CumRevards(void)
  {
   if(CumCounted)
      return;
//---
   for(int i = Buffer_Size - 2; i >= 0; i--)
      Revards[i] += Revards[i + 1] * DiscountFactor;
   CumCounted = true;
  }

Le sugerimos que se familiarice con los métodos de trabajo en los archivos adjuntos. Ahora, vamos a pasar al análisis de los "caballos de batalla" propiamente dichos: nuestros asesores.

Aquí crearemos el primer asesor experto para recopilar la experiencia en el archivo Research.mq5. Planeamos ejecutar este asesor en el modo de optimización del simulador de estrategias para recopilar en paralelo la experiencia de varias pasadas del agente en un episodio de entrenamiento con los datos históricos. Este es exactamente el enfoque que utilizamos en la Fase 1 del artículo anterior. Al igual que en el asesor experto "Fasa1.mql5", utilizaremos los métodos OnTester, OnTesterInit, OnTesterPass y OnTesterDeinit para recopilar y guardar información de diferentes pasadas en un único búfer de acumulación de experiencia, solo que ahora, para seleccionar las acciones, usaremos nuestro modelo, y no un generador de valores aleatorios, como en el asesor experto indicado.

Los parámetros externos de nuestro asesor experto se copiarán de los anteriores sin introducir cambios. En ellos indicaremos el marco temporal y los parámetros de los indicadores utilizados.

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES      TimeFrame   =  PERIOD_H1;
//---
input group                "---- RSI ----"
input int                  RSIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;   //Applied price
//---
input group                "---- CCI ----"
input int                  CCIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL; //Applied price
//---
input group                "---- ATR ----"
input int                  ATRPeriod   =  14;            //Period
//---
input group                "---- MACD ----"
input int                  FastPeriod  =  12;            //Fast
input int                  SlowPeriod  =  26;            //Slow
input int                  SignalPeriod =  9;            //Signal
input ENUM_APPLIED_PRICE   MACDPrice   =  PRICE_CLOSE;   //Applied price
input int                  Agent=1;

Para iniciar el optimizador de estrategias, añadiremos el parámetro Agent. No se utilizará en el código del asesor y solo será necesario para controlar la cantidad de agentes en el optimizador del simulador de estrategias.

En el área de variables globales, declararemos un elemento de la estructura SSstate para registrar el estado actual del sistema, así como una estructura STrayectory para almacenar la experiencia del agente actual, y declararemos un array estático de trayectorias de un elemento, que usaremos para transferir experiencia entre frames.

SState               sState;
STrajectory          Base;
STrajectory          Buffer[];
STrajectory          Frame[1];
CNet                 Actor;
CFQF                 Schedule;
int                  Models = 1;

Aquí especificaremos las variables para crear dos modelos de redes neuronales: agente y planificador. Debemos decir de inmediato que usaremos varios agentes dentro del mismo modelo de agente, pero nos detendremos en este tema con más detalle al describir la arquitectura de los modelos.

No hay innovaciones en el método de inicialización del asesor experto. Inicializaremos los objetos del indicador, la clase comercial. Luego cargaremos los modelos previamente entrenados, y si no hay ninguno, crearemos otros nuevos con parámetros aleatorios. Podrá encontrar el código completo del método en el archivo adjunto.

Ahora queremos detenernos en el método de descripción de la arquitectura del modelo CreateDescriptions. Entrenaremos a nuestros agentes de intenciones utilizando el método Actor-Crítico. Por lo tanto, crearemos una descripción para tres modelos:

  • Agente (Actor)
  • Crítico
  • Planificador (modelo del nivel superior de la jerarquía).

No se alarme porque se hayan declarado variables globales para 2 modelos al crear la descripción de una arquitectura de 3 modelos. El hecho es que en la etapa de recopilación de datos no entrenaremos modelos, y en consecuencia, no se utilizará la funcionalidad de la crítica. Por consiguiente, no crearemos su modelo. 

Al mismo tiempo, para crear modelos comparables, hemos implementado un método general que permite declarar la arquitectura de los modelos, y que utilizaremos tanto en la etapa de recopilación de datos como en la etapa de entrenamiento del modelo.

En los parámetros del método, obtendremos los punteros a los tres objetos para transmitir las arquitecturas de los modelos creados. En el cuerpo del método, verificaremos la relevancia de los punteros recibidos, y, de ser necesario, crearemos nuevos objetos.

bool CreateDescriptions(CArrayObj *actor, CArrayObj *critic, CArrayObj *scheduler)
  {
//---
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }
//---
   if(!critic)
     {
      critic = new CArrayObj();
      if(!critic)
         return false;
     }
//---
   if(!scheduler)
     {
      scheduler = new CArrayObj();
      if(!scheduler)
         return false;
     }

Primero, crearemos la descripción de la arquitectura del Actor (agente). Como siempre, usaremos primero la capa completamente conectada, y detrás vendrá la capa de normalización de datos.

//--- Actor
   actor.Clear();
   CLayerDescription *descr;
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (int)(HistoryBars * 12 + 9);
   descr.window = 0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

A continuación, hemos puesto otra capa completamente conectada.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 300;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Después la capa convolucional intentará resaltar algunos patrones de datos,

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 100;
   descr.window = 3;
   descr.step = 3;
   descr.window_out = 2;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

y procesaremos sus resultados con una capa completamente conectada.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Detrás pondremos otra capa convolucional.

//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 50;
   descr.window = 2;
   descr.step = 2;
   descr.window_out = 4;
   descr.activation = SIGMOID;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Como resultado, un "pastel de capas" de este tipo reducirá la dimensionalidad de los datos a 100 elementos. Esta arquitectura realizará el preprocesamiento de datos.

A continuación, necesitaremos crear algunos agentes de intención. Para no crear modelos múltiples, usaremos nuestras mejores prácticas y utilizaremos la clase de capa neuronal multimodelo totalmente conectada CNeuronMultiModel. Primero, crearemos una capa completamente conectada de tamaño suficiente,

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 1000;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

y luego crearemos 2 capas neuronales ocultas multimodelo totalmente conectadas con 10 modelos cada una.

//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultiModels;
   descr.count = 200;
   descr.window = 100;
   descr.step = 10;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultiModels;
   descr.count = 50;
   descr.window = 200;
   descr.step = 10;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

En la etapa final del modelado, crearemos una capa de salida de resultados, que tendrá su propio "truco". A la salida de nuestro Actor, deberíamos obtener la distribución de probabilidad de las acciones. Cuando analizamos el método de gradiente de políticas, abordamos estos problemas normalizando la salida de la función SoftMax a un único vector de resultados. Ahora necesitaremos normalizar los resultados de 10 modelos.

Al utilizar nuestra capa multimodelo totalmente conectada, los resultados de los 10 modelos se almacenan en una única matriz. Podemos usar nuestra capa CNeuronSoftMaxOCL para normalizar los datos. Al inicializar la capa, indicaremos que necesitamos normalizar una matriz que consta de 10 filas.

//--- layer 9
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultiModels;
   descr.count = 4;
   descr.window = 50;
   descr.step = 10;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 10
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronSoftMaxOCL;
   descr.count = 4;
   descr.step = 10;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Hemos desarrollado un modelo con una única unidad de preprocesamiento de datos seguida de 10 actores paralelos (agentes de intención). Cada de ellos tiene una distribución probabilística de acciones en la salida.

De forma similar, se crea un modelo crítico con 10 críticos como resultado. No obstante, a la salida de la crítica, esperaremos recibir el valor de la función de valor (value) para cada acción. Por lo tanto, no usaremos la capa SoftMax en el modelo crítico.

El modelo de planificador en este algoritmo será un modelo clásico de un nivel. Sin embargo, en el contexto de este algoritmo, el planificador no seleccionará la acción del agente, sino que elegirá un Actor específico de nuestro grupo para seguir su política en la situación actual. El planificador tiene la capacidad de evaluar el estado actual del sistema para seleccionar el agente de intención correspondiente. También podrá consultar los estados de los agentes para tomar una decisión.

En esta implementación, le proponemos suministrar a la entrada del planificador el vector de estado concatenado del sistema analizado y el vector de resultado del conjunto de Actores. Esto permite al planificador usar información sobre el estado del sistema y evaluar los resultados de los actores para seleccionar el actor de intención apropiado.

En la descripción del modelo del planificador, indicaremos la capa de datos de origen del tamaño correspondiente.

//--- Scheduler
   scheduler.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = (int)(HistoryBars * 12 + 9+40);
   descr.window = 0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

Le seguirá una capa de normalización de los datos originales.

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

Para procesar los datos iniciales, se utilizará un enfoque modular similar al descrito anteriormente.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 300;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 100;
   descr.window = 3;
   descr.step = 3;
   descr.window_out = 2;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 50;
   descr.window = 2;
   descr.step = 2;
   descr.window_out = 4;
   descr.activation = SIGMOID;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

El bloque de decisión usará un perceptrón con dos capas ocultas. Esta es una red neuronal multicapa que permite procesar y analizar datos de entrada utilizando varios niveles de abstracción y características de alto nivel. El uso de dos capas ocultas le ofrece al modelo más expresividad y la capacidad de captar relaciones complejas entre las entradas y las salidas.

A la salida de este perceptrón, aplicaremos una función cuantil completamente parametrizada. La función cuantil nos permitirá modelar la distribución condicional de una variable objetivo según los datos de entrada. En lugar de predecir un valor único, nos ofrecerá información sobre la probabilidad de que el valor de la variable objetivo se encuentre en un rango determinado.

El tamaño de la capa de resultados en el bloque de decisión se corresponderá con el tamaño de nuestro conjunto de agentes. Esto significará que cada elemento del vector de resultados representará una probabilidad o puntuación para el agente correspondiente en el grupo. Esto nos permitirá elegir el mejor agente o combinación de agentes según sus puntuaciones y probabilidades.

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronFQF;
   descr.count = 10;
   descr.window_out = 32;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

Las arquitecturas de modelo creadas ofrecen una amplia gama de oportunidades para evaluar el estado actual del sistema y tomar la mejor decisión. Usando redes neuronales multicapa, los modelos pueden analizar diversos aspectos de los datos de entrada y extraer características de alto nivel que pueden asociarse con estrategias y toma de decisiones efectivas.

Esto permitirá que los modelos resuelvan eficazmente problemas con datos limitados o recompensas escasas, y también que se adapten a condiciones y escenarios cambiantes.

El método OnTick merece mención adicional. Al principio, verificaremos si existe una nueva vela abierta y recopilaremos los parámetros para el estado actual del sistema. Este proceso se repetirá sin cambios para los asesores expertos durante varios artículos seguidos y no nos detendremos en ello. Luego realizaremos una pasada directa por los dos modelos y elegiremos la acción del agente en función de sus resultados.

Primero, realizaremos una pasada directa a través del grupo de agentes de intención.

   State1.AssignArray(sState.state);
   if(!Actor.feedForward(GetPointer(State1), 12, true))
      return;

Los resultados obtenidos de la pasada directa de los agentes se concatenarán con la descripción actual del estado del sistema y se transmitirán a la entrada del planificador para su evaluación.

   Actor.getResults(Result);
   State1.AddArray(Result);
   if(!Schedule.feedForward(GetPointer(State1),12,true))
      return;

Después de realizar la pasada directa de ambos modelos, aplicaremos el muestreo para seleccionar un agente de intención específico en función de sus distribuciones. Luego, en el agente seleccionado, muestrearemos una acción específica de su distribución de probabilidad.

   int act = GetAction(Result, Schedule.getSample(), Models);

Debemos destacar que utilizaremos un modelo con parámetros constantes en todas las pasadas, sin entrenamiento. Por lo tanto, la elección codiciosa del agente y la acción con alta probabilidad provocarán la repetición de la misma trayectoria en cada pasada. El muestreo de valores aleatorios de distribuciones nos permite explorar el entorno y obtener diferentes trayectorias en cada pasada. Al mismo tiempo, la restricción impuesta por la distribución nos permite investigar en una dirección determinada.

Al final de la función, realizaremos la acción del agente seleccionado y guardaremos los datos para el entrenamiento posterior.

   switch(act)
     {
      case 0:
         if(!Trade.Buy(Symb.LotsMin(), Symb.Name()))
            act = 3;
         break;
      case 1:
         if(!Trade.Sell(Symb.LotsMin(), Symb.Name()))
            act = 3;
         break;
      case 2:
         for(int i = PositionsTotal() - 1; i >= 0; i--)
            if(PositionGetSymbol(i) == Symb.Name())
               if(!Trade.PositionClose(PositionGetInteger(POSITION_IDENTIFIER)))
                 {
                  act = 3;
                  break;
                 }
         break;
     }
//---
   float reward = 0;
   if(Base.Total > 0)
      reward = ((sState.state[240] + sState.state[241]) - 
               (Base.States[Base.Total - 1].state[240] + Base.States[Base.Total - 1].state[241])) / 10;
   if(!Base.Add(sState, act, reward))
      ExpertRemove();
//---
  }

Después de cada pasada, la información sobre las acciones realizadas, los estados del sistema pasados ​​y la recompensa recibida se almacenarán en un único búfer para el posterior entrenamiento de los modelos. Estas operaciones se realizarán en los métodos OnTester, OnTesterInit, OnTesterPass y OnTesterDeinit, cuyo principio de construcción describimos con detalle en el artículo sobre el algoritmo Go-Explore.

El código completo del asesor y todos sus métodos se puede encontrar en el archivo adjunto.

Tras crear el asesor experto para recopilar la experiencia, lo iniciaremos en el modo de optimización del simulador de estrategias y comenzaremos a trabajar en el asesor de entrenamiento de modelos Study.mq5. En los parámetros externos de este asesor experto, solo indicaremos el número de iteraciones de entrenamiento.

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int                  Iterations     = 100000;

En el bloque de variables globales ya especificaremos 3 modelos: Actor, Crítico y Planificador. Ya hemos descrito la arquitectura de los modelos anteriormente.

STrajectory          Buffer[];
CNet                 Actor;
CNet                 Critic;
CFQF                 Scheduler;

En la función OnInit, primero cargaremos la muestra de entrenamiento que el asesor experto anterior crea para nosotros.

int OnInit()
  {
//---
   ResetLastError();
   if(!LoadTotalBase())
     {
      PrintFormat("Error of load study data: %d", GetLastError());
      return INIT_FAILED;
     }

Luego cargaremos los modelos previamente entrenados o crearemos nuevos modelos,

//--- load models
   float temp;
   if(!Actor.Load(FileName + "Act.nnw", temp, temp, temp, dtStudied, true) ||
      !Critic.Load(FileName + "Crt.nnw", temp, temp, temp, dtStudied, true) ||
      !Scheduler.Load(FileName + "Sch.nnw", dtStudied, true))
     {
      CArrayObj *actor = new CArrayObj();
      CArrayObj *critic = new CArrayObj();
      CArrayObj *schedule = new CArrayObj();
      if(!CreateDescriptions(actor, critic, schedule))
        {
         delete actor;
         delete critic;
         delete schedule;
         return INIT_FAILED;
        }
      if(!Actor.Create(actor) || !Critic.Create(critic) || !Scheduler.Create(schedule))
        {
         delete actor;
         delete critic;
         delete schedule;
         return INIT_FAILED;
        }
      delete actor;
      delete critic;
      delete schedule;
     }
   Scheduler.getResults(SchedulerResult);
   Models = (int)SchedulerResult.Size();
   Actor.getResults(ActorResult);
   Scheduler.SetUpdateTarget(Iterations);
   if(ActorResult.Size() % Models != 0)
     {
      PrintFormat("The scope of the scheduler does not match the scope of the Agent (%d <> %d)", 
                                                                     Models, ActorResult.Size());
      return INIT_FAILED;
     }

e inicializaremos el evento de inicio del proceso de entrenamiento.

//---
   if(!EventChartCustom(ChartID(), 1, 0, 0, "Init"))
     {
      PrintFormat("Error of create study event: %d", GetLastError());
      return INIT_FAILED;
     }
//---
   return(INIT_SUCCEEDED);
  }

En el método Train organizamos el proceso de entrenamiento directo. Es importante tener en cuenta que la muestra de entrenamiento consta de varias pasadas y, en la implementación actual, almacenaremos los estados en una estructura secuencial de trayectoria sin combinarlos todos en una base de datos común. Esto significa que para seleccionar aleatoriamente un estado del sistema, primero deberemos seleccionar una pasada de la matriz y luego seleccionar un estado de esa pasada.

Estrictamente hablando, no asociaremos pasadas ni acciones con agentes de intención específicos. En cambio, todos los agentes se entrenará usando una base común de ejemplos. Este enfoque nos permitirá crear políticas de agentes intercambiables y secuenciales, donde cada agente podrá continuar ejecutando la política desde cualquier estado del sistema, independientemente de qué política se haya aplicado antes de llegar a ese estado.

Al comienzo del método, haremos un pequeño trabajo preparatorio, a saber, determinaremos el número de pasadas en la base de datos de muestra y almacenaremos el valor del contador de ticks para controlar el tiempo del proceso de entrenamiento.

void Train(void)
  {
   int total_tr = ArraySize(Buffer);
   uint ticks = GetTickCount();

Después de realizar el trabajo preparatorio, organizaremos un ciclo del proceso de entrenamiento del modelo.

   for(int iter = 0; (iter < Iterations && !IsStopped()); iter ++)
     {
      int tr = (int)(((double)MathRand() / 32767.0) * (total_tr - 1));
      int i = 0;
      i = (int)((MathRand() * MathRand() / MathPow(32767, 2)) * (Buffer[tr].Total - 2));

Dentro del ciclo de entrenamiento, primero seleccionaremos un pasada de la base de muestras de entrenamiento, como hemos mencionado antes. Luego seleccionaremos aleatoriamente un estado de la pasada seleccionada. Este estado se transmitirá como entrada para la pasada directa de los modelos de Actor y Crítico.

      State1.AssignArray(Buffer[tr].States[i].state);
      if(IsStopped())
        {
         PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
         ExpertRemove();
         return;
        }
      if(!Actor.feedForward(GetPointer(State1), 12, true) ||
         !Critic.feedForward(GetPointer(State1), 12, true))
         return;

Los resultados obtenidos de la pasada directa se descargarán en los vectores correspondientes.

      Actor.getResults(ActorResult);
      Critic.getResults(CriticResult);

El vector de resultado de la pasada directa del Actor se concatenará con el vector de estado del sistema. Este vector combinado luego se introducirá en el modelo del Planificador para su análisis y evaluación.

      State1.AddArray(ActorResult);
      if(!Scheduler.feedForward(GetPointer(State1), 12, true))
         return;

Tras ejecutar la pasada directa del Planificador, aplicaremos la elección codiciosa del agente de intención.

      Scheduler.getResults(SchedulerResult);
      int agent = Scheduler.getAction();
      if(agent < 0)
        {
         iter--;
         continue;
        }

Debemos señalar que al inicio del entrenamiento es posible utilizar el muestreo para explorar el entorno tanto como sea posible. Sin embargo, a medida que el planificador aprenda y mejore su estrategia, pasaremos a una elección codiciosa de agentes. Esto se debe a que el planificador adquiere más experiencia y es capaz de evaluar con mayor precisión el estado del sistema, y también de seleccionar el mejor agente para lograr los objetivos establecidos.

No tomaremos la decisión sobre la elección de la acción, ya que la base de datos de ejemplos ya contiene información sobre las acciones realizadas y las recompensas correspondientes. Usando como base estos datos, generaremos vectores de recompensa para cada modelo y realizaremos una pasada inversa secuencialmente para cada uno de ellos. Primero, realizaremos un pasada inversa del Planificador.

      int actions = (int)(ActorResult.Size() / SchedulerResult.Size());
      float max_value = CriticResult[agent * actions];
      for(int j = 1; j < actions; j++)
         max_value = MathMax(max_value, CriticResult[agent * actions + j]);
      SchedulerResult[agent] = Buffer[tr].Revards[i];
      Result.AssignArray(SchedulerResult);
      //---
      if(!Scheduler.backProp(GetPointer(Result),0.0f,NULL))
         return;

Luego llamaremos el método de pasada inversa del crítico,

      int agent_action = agent * actions + Buffer[tr].Actions[i];
      CriticResult[agent_action] = Buffer[tr].Revards[i];
      Result.AssignArray(CriticResult);
      //---
      if(!Critic.backProp(GetPointer(Result)))
         return;

y le seguirá el modelo de agentes de intención.

      ActorResult.Fill(0);
      ActorResult[agent_action] = Buffer[tr].Revards[i] - max_value;
      Result.AssignArray(ActorResult);
      //---
      if(!Actor.backProp(GetPointer(Result)))
         return;

Al final de las iteraciones del ciclo, verificaremos el tiempo de entrenamiento y mostraremos información al usuario sobre el proceso de aprendizaje cada 0,5 segundos.

      if(GetTickCount() - ticks > 500)
        {
         string str = StringFormat("Actor %.2f%% -> Error %.8f\n", 
                                iter * 100.0 / (double)(Iterations), Actor.getRecentAverageError());
         str += StringFormat("Critic %.2f%% -> Error %.8f\n", 
                                iter * 100.0 / (double)(Iterations), Critic.getRecentAverageError());
         str += StringFormat("Scheduler %.2f%% -> Error %.8f\n", 
                                iter * 100.0 / (double)(Iterations), Scheduler.getRecentAverageError());
         Comment(str);
         ticks = GetTickCount();
        }
     }

Tras completar el proceso de entrenamiento del modelo, mostraremos en el diario de registro los resultados obtenidos e inicializaremos la finalización del asesor experto.

   Comment("");
//---
   PrintFormat("%s -> %d -> %10.7f", __FUNCTION__, __LINE__, Actor.getRecentAverageError());
   PrintFormat("%s -> %d -> %10.7f", __FUNCTION__, __LINE__, Critic.getRecentAverageError());
   PrintFormat("%s -> %d -> %10.7f", __FUNCTION__, __LINE__, Scheduler.getRecentAverageError());
   ExpertRemove();
//---
  }

El código completo del asesor se encuentra en el archivo adjunto. Todos los archivos de este modelo están resaltados en el archivo en el directorio SAC.

El proceso de entrenamiento del modelo constará de iteraciones en las que recopilaremos ejemplos en el modo de optimización y ejecutaremos el proceso de entrenamiento en un gráfico en tiempo real. Si el resultado del entrenamiento no cumple con nuestras expectativas, realizaremos nuevamente la operación de recopilación de ejemplos y volveremos a entrenar los modelos. Estas operaciones se repetirán hasta lograr un resultado óptimo que se corresponda con nuestros objetivos de aprendizaje.

Las iteraciones repetidas de recopilación de ejemplos y de entrenamiento de modelos son una parte integral del proceso de aprendizaje. Nos permiten mejorar los modelos, adaptarlos a las condiciones cambiantes y aspirar a resultados óptimos. Cada iteración nos ofrece nuevos datos y oportunidades para mejorar nuestros modelos, lo cual nos permite resolver problemas de forma más efectiva y lograr nuestros objetivos.

Debemos señalar que el proceso de aprendizaje puede ser iterativo y requerir varios ciclos antes de alcanzar el resultado deseado. Esto se debe a que el entrenamiento de modelos es un proceso complejo que requiere ajustes y mejoras constantes. Tendremos que estar dispuestos a adoptar un enfoque iterativo y repetir la recopilación de ejemplos y entrenamientos hasta alcanzar nuestras metas y obtener resultados óptimos.

Un sistema organizado de tal forma que la base de datos de ejemplos se actualice constantemente con cada pasada posterior de recopilación de ejemplos nos ofrecerá una ventaja significativa. Esto nos permitirá crear una base de datos de ejemplos más completa, lo cual puede mejorar significativamente el entrenamiento del modelo y su capacidad para tomar decisiones óptimas.

No obstante, debemos tener en cuenta que el aumento del tamaño de la base de datos de ejemplos tiene sus consecuencias. En primer lugar, procesar y analizar más datos puede ocupar más tiempo y requerir más recursos informáticos. Esto puede provocar un aumento en el tiempo de iteración del entrenamiento del modelo. En segundo lugar, aumentando el tamaño de la base de datos de ejemplos podemos complicar el entrenamiento, ya que los modelos necesitarán procesar más datos y adaptarse a una gran variedad de escenarios.


3. Simulación

Los resultados del entrenamiento del modelo con los datos históricos del periodo EURUSD H1 para los primeros 4 meses de 2023 han mostrado que el modelo es capaz de generar ganancias tanto en el conjunto de entrenamiento como fuera de él. Hemos realizado más de 10 iteraciones de recopilación de ejemplos y entrenamiento del modelo, incluidas de 8 a 24 pasadas de optimización en cada iteración. En total, hemos recopilado más de 200 pasadas, mientras que el proceso de entrenamiento ha incluido de 100.000 a 10.000.000 de iteraciones.

Para verificar los resultados del entrenamiento del modelo, hemos creado el asesor experto Test.mq5, que ha utilizado una selección codiciosa del agente y la acción en lugar del muestreo. Esto nos ha permitido comprobar el rendimiento del modelo y eliminar el factor aleatorio.

El siguiente gráfico muestra los resultados del modelo ejecutado fuera del conjunto de entrenamiento. En poco tiempo, el modelo ha logrado obtener una pequeña ganancia. El factor de beneficio ha sido de 1,19, mientras que el factor de recuperación ha sido de 0,46.

Sin embargo, merece la pena señalar que hay zonas no rentables en el gráfico de balance, lo cual puede indicar la necesidad de realizar iteraciones adicionales de entrenamiento del modelo. Esto puede ayudarnos a mejorar su capacidad para generar ganancias y reducir el nivel de riesgo en el trading.

Resultados del entrenamiento Resultados del entrenamiento


Conclusión

A nuestro juicio, podemos destacar la efectividad del método Scheduled Auxiliary Control (SAC-X) en el campo del entrenamiento de modelos de agentes de intención para los mercados financieros. SAC-X es una evolución del enfoque clásico de aprendizaje por refuerzo que tiene en cuenta las características específicas de los datos financieros y los requisitos de las estrategias comerciales.

Una de las principales características de SAC-X es el uso de varios modelos (Actor, Crítico, Planificador) para evaluar el estado del sistema y tomar decisiones. Esto le permite considerar varios aspectos del comercio y crear una política de agente más flexible y adaptable.

Otro aspecto importante de SAC-X es el uso del planificador para analizar el estado del sistema y elegir el mejor agente. Esto le permite mejorar la eficiencia y precisión de la toma de decisiones, además de ofrecer resultados comerciales más estables.

Las pruebas de SAC-X con datos históricos de EURUSD han mostrado su capacidad para generar ganancias tanto en el conjunto de entrenamiento como fuera de él. Sin embargo, cabe señalar que en algunos casos hemos hallado zonas de pérdida en el gráfico de balance, lo cual puede indicar la necesidad de un entrenamiento adicional del modelo.

En general, el método Scheduled Auxiliary Control (SAC-X) es una poderosa herramienta para entrenar modelos de agentes de intención en el sector financiero, pues considera las características específicas de los datos del mercado, permitiendo crear estrategias comerciales flexibles y adaptables y mostrando un elevado potencial para lograr una comercio estable y rentable. Una investigación más profunda de SAC-X y la introducción de mejoras en el mismo podrían conducir a resultados aún mejores, así como a la ampliación de su aplicación en los mercados financieros.…


Enlaces

  • Learning by Playing – Solving Sparse Reward Tasks from Scratch
  • Redes neuronales: así de sencillo (Parte 29): Algoritmo actor-crítico con ventaja (Advantage actor-critic)
  • Redes neuronales: así de sencillo (Parte 35): Módulo de curiosidad intrínseca (Intrinsic Curiosity Module)
  • Redes neuronales: así de sencillo (Parte 36): Modelos relacionales de aprendizaje por refuerzo (Relational Reinforcement Learning)
  • Redes neuronales: así de sencillo (Parte 37): Atención dispersa (Sparse Attention)
  • Redes neuronales: así de sencillo (Parte 38): Exploración auto-supervisada por desacuerdo (Self-Supervised Exploration via Disagreement)
  • Redes neuronales: así de sencillo (Parte 39): Go-Explore: un enfoque diferente sobre la exploración
  • Redes neuronales: así de sencillo (Parte 40): Enfoques para utilizar Go-Explore con una gran cantidad de datos


  • Programas usados en el artículo

    # Nombre Tipo Descripción
    1 Research.mq5 Asesor Asesor de recolección de ejemplos
    2 Study.mql5 Asesor Asesor de entrenamiento de modelos
    3 Test.mq5 Asesor Asesor experto para la prueba de modelos
    4 Trajectory.mqh Biblioteca de clases Estructura de descripción del estado del sistema
    5 FQF.mqh Biblioteca de clases Biblioteca de clases de organización de modelos completamente parametrizada
    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/12605

    Archivos adjuntos |
    MQL5.zip (220.03 KB)
    Redes neuronales: así de sencillo (Parte 42): Procrastinación del modelo, causas y métodos de solución Redes neuronales: así de sencillo (Parte 42): Procrastinación del modelo, causas y métodos de solución
    La procrastinación del modelo en el contexto del aprendizaje por refuerzo puede deberse a varias razones, y para solucionar este problema deberemos tomar las medidas pertinentes. El artículo analiza algunas de las posibles causas de la procrastinación del modelo y los métodos para superarlas.
    Características del Wizard MQL5 que debe conocer (Parte 6): Transformada de Fourier Características del Wizard MQL5 que debe conocer (Parte 6): Transformada de Fourier
    La transformada de Fourier, introducida por Joseph Fourier, es un medio para descomponer puntos de datos de ondas complejos en componentes de ondas simples. Esta característica puede resultar útil para los tráders, así que hablaremos de ella en este artículo.
    Algoritmo de recompra: simulación del comercio multidivisa Algoritmo de recompra: simulación del comercio multidivisa
    En este artículo crearemos un modelo matemático para simular la formación de precios multidivisa y completaremos el estudio del principio de diversificación en la búsqueda de mecanismos para aumentar la eficiencia del trading que inicié en el artículo anterior con cálculos teóricos.
    Redes neuronales: así de sencillo (Parte 40): Enfoques para utilizar Go-Explore con una gran cantidad de datos Redes neuronales: así de sencillo (Parte 40): Enfoques para utilizar Go-Explore con una gran cantidad de datos
    Este artículo analizará el uso del algoritmo Go-Explore durante un largo periodo de aprendizaje, ya que la estrategia de elección aleatoria puede no conducir a una pasada rentable a medida que aumenta el tiempo de entrenamiento.