English Русский Português
preview
Redes neuronales en el trading: Señales de trading robustas en cualquier régimen de mercado (Conclusión)

Redes neuronales en el trading: Señales de trading robustas en cualquier régimen de mercado (Conclusión)

MetaTrader 5 — Sistemas comerciales |
27 2
Dmitriy Gizlyk
Dmitriy Gizlyk

Introducción

Los mercados financieros se rigen por las leyes del cambio. Puede que hoy muestren un crecimiento constante, pero mañana podrían reaccionar de forma violenta ante el más mínimo estímulo externo. Los nuevos instrumentos, las iniciativas normativas, las perturbaciones macroeconómicas e incluso el estado de ánimo de la sociedad: todos estos factores modifican el panorama del mercado de forma tan drástica como la construcción de una nueva autopista transforma los flujos de tráfico en una ciudad. Precisamente por eso, los algoritmos clásicos de predicción suelen perder su eficacia en cuanto se enfrentan a nuevas condiciones que no están presentes en el conjunto de entrenamiento.

El framework ST-Expert se propuso como una posible solución a este problema. Su idea central es sencilla y, al mismo tiempo, profunda. En lugar de basarse en una única estrategia válida para todos los casos, forma un conjunto de expertos, cada uno de los cuales está especializado en su propio segmento de la dinámica del mercado. Este enfoque puede compararse con la gestión de una cartera de inversiones. Un gestor con experiencia no cuenta con una única estrategia que funcione en todas las situaciones, sino con todo un conjunto de herramientas. Algunas ofrecen mejores resultados en períodos de tendencia estable, otras generan beneficios en condiciones de gran volatilidad y otras, por su parte, ayudan a preservar el capital durante un mercado lateral prolongado. Al igual que una cartera de este tipo, ST-Expert distribuye su atención entre sus expertos y ofrece una solución equilibrada adaptada a las nuevas condiciones del mercado.

La principal ventaja de este enfoque es su robustez. A diferencia de los modelos que codifican relaciones rígidas entre activos y pierden su eficacia cuando esas relaciones cambian, ST-Expert es capaz de adaptarse. Su arquitectura se basa en una capa de grafones expertos que actúan como generadores de conexiones entre los activos. Cada grafón crea su propia representación de cómo podrían interactuar los mercados o los instrumentos individuales, y el módulo de mezcla selecciona dinámicamente una combinación de expertos en función de la situación actual.

Desde el punto de vista arquitectónico, el framework se basa en el principio de la mezcla de expertos (Mixture of Experts). No se basa en un único «superexperto», sino en todo un conjunto de modelos entrenados en distintos intervalos de tiempo y escenarios. El entrenamiento episódico, en el que el modelo se somete deliberadamente a diversas pruebas de estrés, se convierte en una parte fundamental del proceso. Los datos se dividen en diferentes regímenes de mercado, como fases de tendencia, de corrección o laterales. A continuación, se formula una tarea en la que cada experto se entrena en condiciones específicas. Al mismo tiempo, el módulo de mezcla aprende a combinar los expertos para lograr la máxima eficiencia. Esto es similar a formar a un trader con datos históricos. El trader aprende a tomar decisiones en situaciones de información incompleta y cambios impredecibles. Y eso es precisamente lo que convierte al trader en un trader experimentado.

Desde un punto de vista técnico, la arquitectura ST-Expert es una estructura de múltiples capas. Las series temporales sin procesar se convierten primero en representaciones vectoriales, y a continuación se introducen en bloques de expertos, cada uno de los cuales construye su propia representación de las relaciones ocultas. A continuación, entra en juego el módulo de mezcla, que determina el peso de cada experto para el estado del mercado en cuestión. El resultado es un sistema de previsión dinámico que, en lugar de buscar una fórmula universal, logra un equilibrio entre diferentes estrategias. Como un gestor de carteras que, cada mañana, selecciona los instrumentos que resultan relevantes específicamente para ese día.

Este enfoque combina tres cualidades fundamentales. En primer lugar, la flexibilidad. Una capa de expertos puede integrarse en prácticamente cualquier arquitectura moderna sin alterar la estructura subyacente. En segundo lugar, está la robustez. A diferencia de los modelos tradicionales, que tienden a quedarse estancados en un único patrón, ST-Expert mantiene su precisión incluso cuando cambia el contexto del mercado. Y, por último, está la eficiencia. Esta ampliación de las capacidades se consigue sin un aumento excesivo del número de parámetros. Esto hace que el framework resulte práctico para los sistemas financieros del mundo real, en los que la precisión y la rapidez son fundamentales.

Lo que distingue especialmente a ST-Expert es la eficiencia económica de su implementación. La robustez suele conseguirse a costa de una mayor complejidad computacional. Más parámetros, más datos, más tiempo. En este caso, por el contrario, se obtienen mejoras sin que ello suponga un aumento drástico de los costes. En lo que respecta a los mercados financieros, esto puede compararse con una estrategia de inversión que ofrece una alta rentabilidad con un riesgo moderado y unos costes mínimos.

A continuación se muestra la representación que hace el autor del framework ST-Expert.

Representación del framework ST-Expert realizada por el autor

En nuestro trabajo anterior, ya hemos avanzado mucho: desde los fundamentos teóricos hasta la implementación práctica del framework ST-Expert. Y antes de seguir adelante, merece la pena dedicar un momento a echar la vista atrás y sistematizar lo que hemos tratado, para poder apreciar mejor la magnitud del trabajo realizado y ver qué oportunidades nos depara el futuro.

Ya desde el principio, analizamos en detalle el principio de la mezcla de expertos. Este enfoque es similar a la cartera diversificada de un trader. Cada experto se encarga de su propio ámbito, procesa patrones específicos y lo hace sin interferir con los demás.

A continuación, pasamos de las ideas arquitectónicas abstractas a soluciones algorítmicas concretas. En la práctica, esto se tradujo en la implementación de un conjunto de grafones expertos, cada uno de los cuales desarrolla su propia interpretación de la estructura y las relaciones existentes en los datos. Se prestó especial atención al módulo de mezcla, que constituye el núcleo de todo el sistema. Es este módulo el que aporta la ventaja clave de ST-Expert: la capacidad de seleccionar de forma dinámica a los expertos más adecuados para una situación de mercado concreta. Es como un gestor de carteras con experiencia que puede cambiar de una estrategia a otra en cualquier momento, manteniendo al mismo tiempo un equilibrio entre riesgo y rentabilidad.

La puesta en práctica ha demostrado que el framework no se limita a analizar datos históricos, sino que es capaz de adaptarse a la transformación de dichos datos. Esto es especialmente importante en los mercados financieros, donde los cambios se producen rápidamente y las estrategias obsoletas pueden resultar costosas. La gran capacidad de adaptación del sistema permite predecir los movimientos del mercado con gran precisión, minimizar las pérdidas y aprovechar las nuevas oportunidades.

El camino que hemos seguido ha sentado las bases para seguir trabajando.



Atención global-local

En el artículo anterior, analizamos en detalle y justificamos las decisiones relativas a la implementación de los enfoques del framework ST-Expert en la arquitectura Extralonger. El objetivo principal era integrar grafones en la arquitectura Transformer para mejorar la capacidad del modelo para analizar relaciones complejas en los mercados financieros. En la práctica, implementamos objetos de atención completa y dispersa, lo que nos permitió trabajar con flexibilidad con la información en diferentes niveles de la jerarquía de datos.

Extralonger concede especial importancia al objeto de atención global-local. Permite que dos módulos funcionen en paralelo: un módulo global capaz de identificar patrones a gran escala y un módulo local que se centra en los detalles y las anomalías. Este enfoque permite seguir simultáneamente las tendencias generales del mercado y detectar pequeñas señales que pueden resultar decisivas para las operaciones a corto plazo.

Para facilitar la integración de este concepto en la arquitectura de modelo lineal habitual, estamos desarrollando un nuevo módulo de atención global-local basado en grafones. Este módulo organiza el funcionamiento de los objetos de atención global y local en flujos de información paralelos, seguido de una mezcla adaptativa de los resultados, lo que permite obtener una representación de la información más precisa y flexible.

La clase CNeuronGlobLocGraphAtt puede compararse con un trader experimentado que, al mismo tiempo, sigue las tendencias a largo plazo y detecta las señales a corto plazo. Se basa en la agrupación multicabeza y hereda las capacidades del objeto CNeuronMHAttentionPooling, lo que proporciona una base sólida para un procesamiento flexible de los datos.

class CNeuronGlobLocGraphAtt  :  public CNeuronMHAttentionPooling
  {
protected:
   CNeuronGraphAttention         cGlobal;
   CNeuronSparseGraphAttention   cLocal;
   CNeuronBaseOCL                cConcat;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronGlobLocGraphAtt(void) {};
                    ~CNeuronGlobLocGraphAtt(void) {};
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint window, uint experts, float dropout,
                          uint emb_dimension, uint sparse_dimension,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const   {  return defNeuronGlobLocGraphAtt;   }
   //--- methods for working with files
   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 void      SetActivationFunction(ENUM_ACTIVATION value) override { };
   virtual void      TrainMode(bool flag) override;
  };

Hay dos componentes clave dentro de la clase: cGlobal y cLocal. El primero, al igual que un analista sénior que analiza el mercado en su conjunto, revisa toda la secuencia de datos, identifica patrones generales y tendencias a largo plazo, y contribuye a elaborar previsiones estratégicas. El segundo actúa como un táctico ágil, centrándose en los acontecimientos inmediatos y las anomalías que pueden afectar al instante a la posición del trader.

Todos los componentes internos del módulo se declaran como miembros directos de la clase, lo que nos permite dejar vacíos el constructor y el destructor de la clase. La inicialización se lleva a cabo en el método Init. Este enfoque recuerda al de un trader experimentado que entra en el mercado con un equipo de analistas ya formado. Cada especialista conoce cuál es su función. Y la labor del responsable es asignarles las funciones adecuadas y organizar su colaboración.
bool CNeuronGlobLocGraphAtt::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                  uint units, uint window, uint experts, float dropout,
                                  uint emb_dimension, uint sparse_dimension,
                                  ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, window, units,
                                       2, optimization_type, batch))
      return false;

El método Init se encarga de configurar la arquitectura de nuestro módulo de atención global-local. En primer lugar, se invoca el método de inicialización de la clase padre CNeuronMHAttentionPooling. Si se producen errores en esta fase, el proceso se detiene. Es como analizar los fundamentos de un activo antes de abrir una posición: sin una base sólida, no tiene sentido seguir adelante.

Una vez completada correctamente la inicialización de la clase base, comienza la inicialización de los objetos internos. En primer lugar, se configura cGlobal, que se encarga de la atención global. Procesa toda la secuencia de datos y elabora una visión estratégica del mercado, identificando tendencias y patrones a largo plazo. Si este componente no se inicializa correctamente, el módulo simplemente no puede seguir funcionando, lo que pone de relieve la importancia fundamental del análisis global.

   int index = 0;
   if(!cGlobal.Init(0, index, OpenCL, iUnits, iWindow, emb_dimension, experts, dropout,
                    optimization, iBatch))
      return false;

A continuación, se crea cLocal, un objeto de atención local. Se centra en los acontecimientos recientes y los patrones poco frecuentes, lo que permite al modelo detectar señales relevantes y anomalías que podrían pasar desapercibidas en un análisis global. También se comprueba que la inicialización de cLocal sea correcta; si se produce un error, el módulo no se inicia, para no exponer al sistema a decisiones erróneas. Como un trader que no abre una posición sin contar con datos fiables.

   index++;
   if(!cLocal.Init(0, index, OpenCL, iUnits, iWindow, experts, dropout, emb_dimension,
                   sparse_dimension, optimization, iBatch))
      return false;

Por último, los resultados de la atención global y local se combinan en cConcat. Se trata simplemente de un objeto de almacenamiento para el tensor concatenado de los resultados de dos perspectivas del mercado. Tenemos previsto realizar la combinación efectiva para obtener el valor final utilizando la clase padre.

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

Si todos los pasos se completan correctamente, el método Init devuelve true, lo que indica que el módulo está listo para funcionar. De lo contrario, se da por finalizada la inicialización, lo que garantiza la estabilidad y la fiabilidad del funcionamiento del modelo.

El resultado es una arquitectura completa en la que la atención global y la local funcionan en flujos paralelos, y la mezcla adaptativa garantiza una interpretación precisa y flexible de los datos del mercado. El módulo se convierte en una herramienta dinámica capaz tanto de tener una visión estratégica como de responder a los cambios a corto plazo, al igual que un operador experimentado que sigue de cerca el pulso del mercado.

Una vez que el objeto se ha inicializado correctamente, el siguiente paso consiste en configurar la propagación hacia delante de los datos a través del módulo, que se implementa en el método feedForward. Esta etapa puede compararse con la de un trader que opera en tiempo real. Las señales del mercado llegan de forma continua, y el sistema debe procesarlas al instante, combinando una visión estratégica con una respuesta a las fluctuaciones a corto plazo.

bool CNeuronGlobLocGraphAtt::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cGlobal.FeedForward(NeuronOCL))
      return false;

En primer lugar, los datos pasan por el objeto de atención global cGlobal, que ofrece una visión general del mercado e identifica las tendencias a largo plazo y los patrones estructurales. Si se producen errores en esta fase, la propagación hacia delante se interrumpe, ya que, sin el contexto global, el análisis posterior carece de sentido, al igual que una estrategia de cartera sin un conocimiento de las condiciones macroeconómicas.

A continuación, la información se envía al objeto de atención local cLocal, que identifica detalles clave, señales sutiles y anomalías locales. Este componente funciona en paralelo con el componente global, centrándose en los acontecimientos que pueden tener un impacto inmediato en la posición del modelo.

   if(!cLocal.FeedForward(NeuronOCL))
      return false;

Tras el procesamiento por parte de cada módulo, sus resultados se concatenan para cada elemento individual de la secuencia.

   if(!Concat(cGlobal.getOutput(), cLocal.getOutput(), cConcat.getOutput(),
              iWindow, iWindow, iUnits))
      return false;
//---
   return CNeuronMHAttentionPooling::feedForward(cConcat.AsObject());
  }

A continuación, los resultados de los análisis globales y locales se combinan de forma adaptativa, creando una única señal coherente para su posterior procesamiento. Esta etapa puede compararse con la combinación de los informes estratégicos y tácticos de dos equipos de analistas, de modo que el trader disponga de una visión completa del mercado y pueda tomar decisiones fundamentadas.

De este modo, el método garantiza un flujo continuo y coordinado de información en todos los niveles del modelo, lo que permite realizar un seguimiento simultáneo tanto de las tendencias globales como de los acontecimientos locales del mercado. El módulo se convierte en una herramienta dinámica capaz de responder de forma adaptativa a los cambios y de generar previsiones precisas en un contexto de datos financieros muy cambiantes.

Sin embargo, la previsión obtenida utilizando parámetros aleatorios del modelo no aporta mucha información. Esto no es diferente a adivinar el futuro leyendo las hojas de té, o a intentar predecir los movimientos del mercado sin datos históricos ni análisis. Para que el modelo funcione de forma eficaz, el error total debe distribuirse adecuadamente entre sus componentes, teniendo en cuenta su impacto en el resultado final. Esta es precisamente la tarea que realiza el método calcInputGradients.

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

En primer lugar, el método comprueba si el puntero recibido al objeto de datos de origen NeuronOCL es válido, ya que sin él no es posible realizar los cálculos. A continuación, se invoca el mecanismo básico de retropropagación de la clase padre CNeuronMHAttentionPooling, que distribuye los gradientes en el nivel de la señal agrupada.

   if(!CNeuronMHAttentionPooling::calcInputGradients(cConcat.AsObject()))
      return false;
   if(!DeConcat(cGlobal.getGradient(), cLocal.getGradient(), cConcat.getGradient(),
                iWindow, iWindow, iUnits))
      return false;

A continuación tiene lugar el proceso de división del gradiente. El método DeConcat distribuye cuidadosamente el error entre la atención global y la local, garantizando que cada parte del modelo tenga en cuenta correctamente su impacto en la predicción global.

A continuación, debemos transmitir los gradientes hasta el nivel de los datos de origen a través de dos flujos de información: la atención global y la atención local. En primer lugar, vamos a procesar el módulo global.

   if(!NeuronOCL.CalcHiddenGradients(cGlobal.AsObject()))
      return false;
   CBufferFloat *temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(PrevOutput, false))
      return false;
   if(!NeuronOCL.CalcHiddenGradients(cLocal.AsObject()))
      return false;

A continuación, se almacena temporalmente un puntero al búfer de gradientes en una variable local, de modo que la información para la atención local pueda distribuirse correctamente sin perder los datos obtenidos anteriormente. Solo entonces se calculan los gradientes ocultos para el módulo local.

Por último, se suman los valores de las dos vías de información, lo que garantiza la coherencia entre los flujos de datos globales y locales, y restablecemos los punteros a los búferes de datos a su estado original.

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

En consecuencia, el método asegura una distribución adecuada del error entre todos los componentes del módulo, lo que garantiza que el entrenamiento se desarrolle de forma equilibrada y que cada elemento del modelo contribuya a la precisión de las predicciones. Este proceso puede compararse con el de un trader experimentado que, tras analizar los resultados de su equipo, ajusta las actuaciones de cada analista para que la cartera global de estrategias siga siendo óptima y robusta frente a las fluctuaciones del mercado.

Una vez calculados los gradientes, el siguiente paso consiste en ajustar los parámetros del modelo, lo cual se lleva a cabo en el método updateInputWeights. En este caso, no creamos nuevos algoritmos, sino que simplemente pasamos el control de forma secuencial a los componentes internos del módulo, lo que permite que cada uno de ellos ajuste sus propios pesos de forma independiente en función de los gradientes recibidos.

bool CNeuronGlobLocGraphAtt::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cGlobal.UpdateInputWeights(NeuronOCL))
      return false;
   if(!cLocal.UpdateInputWeights(NeuronOCL))
      return false;
//---
   return CNeuronMHAttentionPooling::updateInputWeights(cConcat.AsObject());
  }

En general, CNeuronGlobLocGraphAtt funciona como un analista de mercado en tiempo real, combinando una visión estratégica con una respuesta inmediata a los acontecimientos. Permite al modelo analizar de forma eficaz los flujos de información multidimensionales, predecir el comportamiento de los mercados financieros y adaptarse a la volatilidad. Como un trader experimentado que nunca pierde de vista el mercado y puede ajustar sus posiciones al instante en respuesta a nuevas señales.

El código completo del objeto y de todos sus métodos se incluye en el archivo adjunto.



Objeto de nivel superior

Una vez creados todos los componentes necesarios, el siguiente paso consiste en integrar los nuevos objetos directamente en la arquitectura del framework Extralonger creado anteriormente. Recordemos que, al trabajar en este framework, inicialmente creamos un objeto de nivel superior en cuyo interior declaramos cuatro arrays dinámicos para almacenar punteros a secuencias de componentes internos. Esta decisión nos dio flexibilidad. Ahora bien, para añadir nuevos elementos, no es necesario crear un objeto desde cero; basta con heredar la funcionalidad de un objeto ya existente y sobrescribir únicamente el método de inicialización, que define la arquitectura del nuevo módulo. Así es exactamente como se crea la clase CNeuronExtralongerGraph.

class CNeuronExtralongerGraph   :  public CNeuronExtralonger
  {
public:
                     CNeuronExtralongerGraph(void) {};
                    ~CNeuronExtralongerGraph(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint time_steps_in, uint time_steps_out, uint variables,
                          uint dimension, uint emb_dimension, uint period1,
                          uint frame1, uint period2, uint frame2, uint layers,
                          uint experts, uint m_units, float sparse,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   const   {  return defNeuronExtralongerGraph;   }
  };

El constructor y el destructor se han dejado vacíos, ya que todas las estructuras internas se inicializan en el método Init. Su objetivo es reunir elementos dispares en una estructura unificada capaz de seguir simultáneamente las tendencias a largo plazo, las fluctuaciones a corto plazo y las relaciones espaciales entre las variables.

bool CNeuronExtralongerGraph::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint time_steps_in, uint time_steps_out, uint variables,
                                   uint dimension, uint emb_dimension, uint period1,
                                   uint frame1, uint period2, uint frame2, uint layers,
                                   uint experts, uint m_units, float sparse,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, variables, time_steps_out,
                                       3, optimization_type, batch))
      return false;

La inicialización comienza con una agrupación multicabeza básica. Es como sentar los cimientos de un rascacielos. Si los cimientos son inestables, las capas posteriores no sirven de nada. A continuación, se crea el bloque de proyección temporal, que puede compararse con el «radar» temporal del modelo, capaz de realizar un seguimiento de la dinámica del mercado en distintos horizontes temporales. Esta etapa se encarga de transformar la serie temporal original en representaciones vectoriales multidimensionales, que constituyen un formato práctico y rico en información para su posterior procesamiento.

   CNeuronBatchNormOCL           *norm    = NULL;
   CNeuronConvOCL                *conv    = NULL;
   CNeuronTransposeOCL           *transp  = NULL;
   CNeuronLearnabledPE           *lnoise  = NULL;
   CNeuronSpatialEmbedding       *semb    = NULL;
   CNeuronTempEmbedding          *temb    = NULL;
   CNeuronGraphAttention         *att     = NULL;
   CNeuronGlobLocGraphAtt        *glatt   = NULL;
//--- Time projection
   cProjectionT.Clear();
   cProjectionT.SetOpenCL(OpenCL);
   int index = 0;
   lnoise = new CNeuronLearnabledPE();
   if(!lnoise ||
      !lnoise.Init(0, index, OpenCL, time_steps_in * variables, optimization, iBatch) ||
      !cProjectionT.Add(lnoise))
     {
      DeleteObj(lnoise);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, variables, variables, dimension, time_steps_in, 1,
                 optimization, iBatch) ||
      !cProjectionT.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   temb = new CNeuronTempEmbedding();
   uint half_emb = (emb_dimension + 1) / 2;
   if(!temb ||
      !temb.Init(0, index, OpenCL, time_steps_in, dimension, half_emb, period1,
                 frame1, emb_dimension - half_emb, period2, frame2, optimization, iBatch) ||
      !cProjectionT.Add(temb))
     {
      DeleteObj(temb);
      return false;
     }
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, temb.Neurons(), iBatch, optimization) ||
      !cProjectionT.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }

Esta etapa se ha heredado íntegramente de la clase base sin ningún cambio. A modo de breve recordatorio, la proyección comienza con el objeto de codificación posicional aprendible (LPE). En este caso, añade ruido aprendible, lo que ayuda a que el modelo sea más robusto frente a la volatilidad del mercado y al ruido de los datos. A continuación, los datos pasan por capas convolucionales, que identifican patrones locales y características estructurales en la serie temporal, revelando tendencias a corto plazo y anomalías. A continuación, se añaden representaciones vectoriales temporales (TempEmbedding) a los datos. El último paso es la normalización por lotes, que estabiliza las señales y elimina los desequilibrios entre los distintos componentes de las representaciones vectoriales. Como resultado, el modelo recibe una entrada limpia y coherente para los módulos de atención posteriores, lo que minimiza el riesgo de acumulación de errores en una fase temprana.

El siguiente paso es el módulo temporal. Consta de varias capas de atención en grafos, complementadas con capas convolucionales en el bloque de proyección de datos.

//--- Time Module
   cTimeModule.Clear();
   cTimeModule.SetOpenCL(OpenCL);
   for(uint i = 0; i < layers; i++)
     {
      index++;
      att = new CNeuronGraphAttention();
      if(!att ||
         !att.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension,
                   emb_dimension, experts, sparse, optimization, iBatch) ||
         !cTimeModule.Add(att))
        {
         DeleteObj(att);
         return false;
        }
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension,
                 variables, time_steps_in, 1, optimization, iBatch) ||
      !cTimeModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(TANH);
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) ||
      !cTimeModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, variables,
                 1, optimization, iBatch) ||
      !cTimeModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, variables,
                 1, optimization, iBatch) ||
      !cTimeModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cTimeModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) ||
      !cTimeModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }

Desde el punto de vista estructural, la arquitectura del bloque no ha sufrido cambios. Simplemente sustituimos los módulos del Transformer clásico por nuestros nuevos módulos de atención en grafos. Estos componentes permiten al modelo identificar relaciones complejas en series temporales, de forma muy similar a como lo haría un equipo de análisis que realiza un seguimiento de las tendencias y responde a las anomalías a corto plazo de forma simultánea.

Tras el análisis temporal, se crea el módulo de mezcla. Este bloque incorpora cuidadosamente nuevas capas de atención en grafos y módulos de atención global-local con grafones.

//--- Mix Module
   cMixModule.Clear();
   cMixModule.SetOpenCL(OpenCL);
   uint att_layers = (layers + 1) / 2;
   for(uint i = 0; i < att_layers; i++)
     {
      index++;
      att = new CNeuronGraphAttention();
      if(!att ||
         !att.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension,
                   emb_dimension, experts, sparse, optimization, iBatch) ||
         !cMixModule.Add(att))
        {
         DeleteObj(att);
         return false;
        }
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension, optimization, iBatch) ||
      !cMixModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   for(uint i = (att_layers == layers ? 0 : att_layers - 1); i < layers; i++)
     {
      index++;
      glatt = new CNeuronGlobLocGraphAtt();
      if(!glatt ||
         !glatt.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_in, experts, sparse,
                     emb_dimension, uint(sparse * (dimension + emb_dimension)), optimization, iBatch) ||
         !cMixModule.Add(glatt))
        {
         DeleteObj(glatt);
         return false;
        }
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, dimension + emb_dimension,
                                                                                1, optimization, iBatch) ||
      !cMixModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out,
                 dimension + emb_dimension, 1, optimization, iBatch) ||
      !cMixModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(TANH);
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_out, optimization, iBatch) ||
      !cMixModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, variables,
                 time_steps_out, 1, optimization, iBatch) ||
      !cMixModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cMixModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }

Las capas convolucionales proyectan los resultados del análisis en un horizonte de predicción determinado. Cada objeto se somete a una comprobación de validez y solo entonces se incluye en el flujo de datos global, lo que garantiza la continuidad y la coherencia en el procesamiento.

El módulo espacial completa la cadena al procesar las dependencias espaciales entre variables. En este caso, el primer paso consiste en proyectar los datos brutos en representaciones vectoriales espaciales y, a continuación, normalizarlas.

//--- Spatial Module
   cSpatialModule.Clear();
   cSpatialModule.SetOpenCL(OpenCL);
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) ||
      !cSpatialModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, dimension, variables,
                 1, optimization, iBatch) ||
      !cSpatialModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   semb = new CNeuronSpatialEmbedding();
   if(!semb ||
      !semb.Init(0, index, OpenCL, variables, dimension, emb_dimension, optimization, iBatch) ||
      !cSpatialModule.Add(semb))
     {
      DeleteObj(semb);
      return false;
     }
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, semb.Neurons(), iBatch, optimization) ||
      !cSpatialModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }

Los módulos de atención global-local con grafones permiten tener en cuenta simultáneamente las correlaciones locales y los patrones globales, generando así una señal precisa. Esta etapa puede compararse con la labor de un gestor de carteras que coordina el trabajo de varios equipos de analistas, teniendo en cuenta tanto las condiciones macroeconómicas como los cambios a pequeña escala en el mercado.

   for(uint i = 0; i < layers; i++)
     {
      index++;
      glatt = new CNeuronGlobLocGraphAtt();
      if(!glatt ||
         !glatt.Init(0, index, OpenCL, variables, dimension + emb_dimension, experts, sparse,
                     emb_dimension, m_units, optimization, iBatch) ||
         !cSpatialModule.Add(glatt))
        {
         DeleteObj(glatt);
         return false;
        }
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension,
                 time_steps_out, variables, 1, optimization, iBatch) ||
      !cSpatialModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out,
                 variables, 1, optimization, iBatch) ||
      !cSpatialModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cMixModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) ||
      !cMixModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }

Al igual que en los dos módulos anteriores, los resultados del análisis se proyectan sobre un horizonte de predicción específico mediante un bloque de capas convolucionales.

Por último, los tres flujos de información se combinan en el objeto cConcatResults, que los transmite a la clase padre para que esta integre los resultados de forma cuidadosa y adaptativa en una previsión final.

   index++;
   if(!cConcatResults.Init(0, index, OpenCL, 3 * variables * time_steps_out, optimization, iBatch))
      return false;
   cConcatResults.SetActivationFunction(None);
//---
   return true;
  }

En definitiva, el método Init crea una arquitectura dinámica y flexible en la que cada módulo desempeña su propia función especializada sin dejar de estar integrado en el sistema global. El modelo es capaz de seguir simultáneamente las dependencias temporales y espaciales, adaptarse a la volatilidad del mercado y generar previsiones precisas incluso en condiciones difíciles. Este proceso puede compararse con un equipo analítico altamente profesional, en el que cada miembro realiza su propia tarea y, juntos, crean la visión más precisa posible del mercado.

Los algoritmos de propagación hacia delante y retropropagación del nuevo módulo se heredan íntegramente de la funcionalidad de la clase padre, lo que permite a los desarrolladores centrarse en las características arquitectónicas y estructurales sin tener que reescribir los mecanismos básicos de procesamiento de señales. Este enfoque garantiza la máxima fiabilidad y coherencia en el funcionamiento del modelo, ya que todos los procedimientos computacionales ya han sido verificados y, gracias a la herencia, no es necesario implementar por separado en el nuevo objeto los métodos feedForward, calcInputGradients y updateInputWeights.

El código completo de la clase y de todos sus métodos se incluye en el archivo adjunto, lo que permite examinar en detalle la arquitectura y la secuencia de inicialización de cada elemento interno, así como evaluar cómo interactúan entre sí todos los bloques dentro de un modelo unificado.



Arquitectura del modelo

Poco a poco nos acercamos a la conclusión lógica de nuestro trabajo y, tras implementar todos los componentes necesarios, pasamos a describir la arquitectura del propio modelo. Es importante destacar aquí un aspecto clave. Nuestro objetivo va más allá de la mera previsión de series de precios. Al igual que antes, estamos desarrollando un robot de trading completo: un agente inteligente y autónomo capaz de analizar el mercado y tomar decisiones, abrir y cerrar posiciones, y adaptarse a las condiciones del mercado en constante cambio. La previsión de precios se convierte simplemente en una herramienta: un codificador del estado del mercado, un sensor que transforma datos financieros caóticos en una representación estructurada que el modelo puede comprender.

Nuestro enfoque se basa en el concepto actor-crítico, que nos permite separar las funciones de generar acciones y evaluar su calidad. Definimos tres modelos funcionales: el codificador, el actor y el crítico. El codificador analiza los datos históricos e identifica patrones; el actor toma decisiones basadas en el estado actual del mercado; y el crítico evalúa los resultados de esas acciones y ajusta la estrategia, minimizando las pérdidas financieras y mejorando la eficiencia de las operaciones.

La arquitectura del modelo se crea en el método CreateDescriptions, que construye minuciosamente matrices de descripciones de capas para cada una de las tres partes del modelo.

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

En el codificador, los datos que se analizan son recibidos por una capa básica totalmente conectada, que actúa únicamente como búfer de recepción.

//--- Encoder
   encoder.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   uint prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormWithNoise;
   descr.count = prev_count;
   descr.batch = BatchSize;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

A continuación, se utiliza una capa de normalización por lotes, con adición de ruido aleatorio como técnica adicional de aumento de datos. Estabiliza la señal y, al mismo tiempo, añade una fluctuación controlada que imita la incertidumbre del mercado.

La siguiente capa —ConcatDiff— desempeña un papel fundamental en la preparación de los datos para su análisis en profundidad. Amplía el espacio de características calculando las primeras diferencias entre pasos temporales adyacentes. Este enfoque permite al modelo reflejar la dinámica de las variaciones de precios, en lugar de limitarse a sus valores absolutos, lo cual resulta especialmente importante en el caso de las series temporales financieras, en las que la tasa y la dirección del cambio suelen ser más importantes que los propios valores.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConcatDiff;
   prev_count = descr.count = HistoryBars;
   descr.layers = BarDescr;
   descr.step = 1;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   uint prev_out = descr.layers*2 ;

Puedes imaginarte esta capa como un trader con experiencia que no solo observa el precio actual, sino que también analiza su ritmo de movimiento y su aceleración: las primeras diferencias proporcionan al modelo información sobre tendencias y cambios de tendencia, lo que le ayuda a identificar patrones en la dinámica multidimensional del mercado. Como resultado, ConcatDiff genera un conjunto de características estructurado e informativo, que posteriormente se transmite a las capas siguientes y proporciona una base sólida para el análisis y la predicción complejos, incluido el funcionamiento de los módulos de atención en grafos y de atención global-local.

La capa ExtralongerGraph ocupa un lugar especial. Es el núcleo del modelo, que integra representaciones vectoriales temporales y espaciales, atención en grafos y atención global-local. Aquí se configuran los parámetros relativos a los pasos temporales del historial y de la predicción, los períodos de análisis y los marcos temporales, el número de expertos y el tamaño de la representación vectorial. Esta capa transforma los datos históricos en una representación multidimensional del mercado, lo que le permite tener en cuenta simultáneamente las fluctuaciones a corto plazo y las tendencias a largo plazo, como si se tratara de un equipo de analistas que trabajan de forma coordinada: cada uno realiza un seguimiento de un aspecto diferente de la dinámica del mercado y, a continuación, todos combinan los resultados en una única previsión.

//--- Layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronExtralongerGraph;
     {
      uint temp[] = {HistoryBars,               // History Time Steps
                     NForecast,                 // Forecast Time Steps
                     ShortPeriod,               // Period 2
                     LongPeriod,                // Period 2
                     BarDescr/2                 // M units
                    };
      if(ArrayCopy(descr.units, temp) < (int)temp.Size())
         return false;
     }
   prev_count = descr.units[1];
   descr.window = prev_out;                     // Variables
   descr.window_out = EmbeddingSize;            // Internal Dimension
     {
      uint temp[] = {EmbeddingSize,             // Embedding Dimension
                     PeriodSeconds(PERIOD_H1),  // Frame 1
                     PeriodSeconds(PERIOD_D1)   // Frame 2
                    };
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
   descr.layers=2;
   descr.step=NExperts;                         //Experts
   descr.probability=0.3f;
   descr.optimization=ADAM;
   descr.batch=BatchSize;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   uint window=descr.window;
   uint count=prev_count;

Una capa convolucional y la desnormalización completan el codificador y actúan como una especie de filtro y corrector de las características resultantes. En esta fase, ya disponemos de una previsión generada por el módulo ExtralongerGraph, pero es importante recordar que anteriormente ampliamos el espacio de características añadiendo primeras diferencias. Estas características adicionales permiten al modelo realizar un seguimiento no solo de los valores absolutos de los precios, sino también de la tasa de variación de los mismos, lo cual resulta fundamental para identificar tendencias y cambios de tendencia en el mercado financiero.

La capa convolucional reduce la dimensionalidad de las características, lo que simplifica el procesamiento y permite que el modelo se centre en los patrones clave. La función de activación TANH limita la amplitud de las señales, suavizando las fluctuaciones bruscas y evitando una sensibilidad excesiva al ruido, lo cual resulta especialmente importante en condiciones de alta volatilidad. Como resultado, el objeto genera una señal en el intervalo comprendido entre -1 y 1, lo cual es característico de los datos normalizados. El objeto RevInDenorm devuelve los valores resultantes a la escala de los datos originales y, al mismo tiempo, añade los parámetros estadísticos de la serie analizada que se extrajeron durante la normalización inicial. Así pues, en la salida del modelo obtenemos valores previstos en el contexto habitual del nivel de mercado original: la media, la varianza y otras características que permiten comparar los valores previstos con los valores reales.

Como resultado, el codificador genera una representación rica, multidimensional y estabilizada del mercado, lista para transmitirse a los bloques actor y crítico, donde se utilizará para tomar decisiones de negociación y evaluar su calidad.

La arquitectura de los modelos actor y crítico se ha tomado íntegramente de trabajos anteriores sin ningún cambio. Y no nos detendremos en un análisis detallado de su estructura, ya que nos centraremos principalmente en el funcionamiento del codificador y en la preparación de las características. En el archivo adjunto se incluye el código completo que describe la arquitectura de todos los modelos, lo que te permitirá revisar su estructura interna si fuera necesario.


Pruebas

Antes de confiar dinero real al modelo, realizamos pruebas retrospectivas exhaustivas de la estrategia utilizando datos históricos, sometiéndola a diversos escenarios de mercado, como si estuviéramos perfeccionando nuestras habilidades de evaluación de riesgos y toma de decisiones en una amplia variedad de condiciones.

La primera fase —el entrenamiento fuera de línea— se llevó a cabo utilizando datos históricos del par de divisas EURUSD en el marco temporal H1, correspondientes al periodo comprendido entre enero de 2024 y junio de 2025. Este periodo resultó ser un auténtico campo de pruebas para el modelo. El entorno tan variado permitió al modelo perfeccionar su capacidad para reconocer señales clave, tomar decisiones de negociación sólidas y mantener el rumbo incluso en las situaciones más caóticas. En esta fase, el modelo no solo estaba aprendiendo a repetir patrones históricos, sino también a comprender las regularidades del mercado, adaptándose a la dinámica de los precios y al volumen de negociación. Al igual que un trader con experiencia que analiza el mercado utilizando a la vez la intuición y la estrategia.

Tras completar con éxito el entrenamiento fuera de línea, pasamos a la segunda fase: el ajuste fino en línea en el Probador de estrategias de MetaTrader 5. En este caso, los datos llegaban en tiempo real, una vela tras otra, y el modelo aprendió a gestionar el flujo del mercado a velocidades reales. Aprendió a desenvolverse ante la dinámica de las fluctuaciones reales de los precios, a mantenerse estable ante el ruido del mercado, a adaptar sus acciones en períodos de baja liquidez y a reaccionar al instante ante picos repentinos. Esta etapa sirvió para perfeccionar en cierta medida la estrategia. La estructura subyacente, basada en datos históricos, se mantuvo sin cambios, pero el modelo aprendió a adaptarse a las condiciones actuales del mercado, minimizando el riesgo de sobreajuste y mejorando su capacidad para tomar decisiones en un entorno impredecible.

La prueba final se llevó a cabo utilizando datos de julio y agosto de 2025: datos totalmente nuevos que no se habían utilizado anteriormente. Todos los parámetros obtenidos en las etapas anteriores se cargaron sin modificaciones, lo que permitió realizar una evaluación imparcial de la capacidad de generalización del modelo. A continuación se presentan los resultados de las pruebas.

Los resultados de las pruebas del modelo demuestran la eficacia de su entrenamiento y su capacidad para adaptarse a las condiciones reales del mercado. El gráfico de balance y patrimonio muestra que el modelo mantiene un crecimiento constante del capital a pesar de la volatilidad del mercado. Las líneas de balance y equidad son prácticamente idénticas, lo que indica una baja reducción del capital y una gestión precisa del riesgo.

Un análisis de las estadísticas de las operaciones confirma la eficacia de la estrategia. El modelo generó un beneficio neto total de 79,72 USD a partir de un depósito inicial de 100,0 USD. El beneficio total (beneficio bruto) ascendió a 315,47 USD, mientras que las pérdidas totales (pérdidas brutas) ascendieron a 235,75 USD, lo que dio como resultado un factor de beneficio de 1,34. Esto indica que cada dólar ganado generó aproximadamente 34 céntimos de beneficio neto tras deducir las pérdidas, lo cual es un resultado respetable para una estrategia que opera en tiempo real.

Es importante señalar que la estrategia presenta reducciones moderadas: la reducción máxima del balance fue del 30,05 %, mientras que la reducción máxima del patrimonio fue del 43,01 %. Al mismo tiempo, el factor de recuperación es de 1,07 y el ratio de Sharpe alcanza 2,59. Todo esto apunta a una buena relación riesgo-rentabilidad.

El modelo mostró un rendimiento equilibrado tanto en posiciones cortas como en posiciones largas: 17 operaciones cortas con una tasa de acierto del 64,71 % y 18 operaciones largas con una tasa de acierto del 55,56 %. Se ejecutaron un total de 69 operaciones, de las cuales 21 fueron rentables (60 %) y 14 fueron no rentables (40 %).

En general, las pruebas confirman que la aplicación de los enfoques desarrollados y el entrenamiento con datos históricos, seguidos de un ajuste fino en tiempo real, permitieron que el modelo respondiera de forma eficaz a las fluctuaciones del mercado y demostrara una gran robustez y un comportamiento predecible en la gestión del capital.



Conclusión

Hemos completado la implementación de los enfoques propuestos por los autores del framework ST-Expert y los hemos integrado con éxito en el framework Extralonger. La arquitectura resultante permite analizar simultáneamente las representaciones temporales y espaciales de los datos, combinando las tendencias globales con las anomalías locales, lo que mejora significativamente la precisión de las previsiones y la robustez de las estrategias.

Se prestó especial atención a la reproducibilidad y al determinismo de todos los módulos: desde la inicialización y la propagación hacia delante hasta el cálculo de gradientes y las actualizaciones de pesos. Este enfoque garantiza un comportamiento estable del sistema en distintos modos de funcionamiento y simplifica la depuración de interacciones complejas entre componentes.

Los resultados de las pruebas, basados en datos históricos reales, demostraron la eficacia del modelo y su capacidad para adaptarse a diversas condiciones de mercado. Los resultados confirman que la combinación de la atención global y local en grafos con representaciones vectoriales temporales y espaciales sienta una base sólida para su aplicación práctica en el mercado, lo que permite a los modelos adaptarse a condiciones cambiantes y ofrecer previsiones de gran precisión, al tiempo que se mantienen bajo control los costes computacionales.


Enlaces


Programas utilizados en este artículo

# Nombre Tipo Descripción
1 Study.mq5 Asesor experto Asesor experto para el entrenamiento de modelos fuera de línea
2 StudyOnline.mq5 Asesor experto Asesor experto para el entrenamiento de modelos en línea
3 Test.mq5 Asesor experto Asesor experto para las pruebas de modelos
4 Trajectory.mqh Biblioteca de clases Estructura que describe el estado del sistema y la arquitectura del modelo
5 NeuroNet.mqh Biblioteca de clases Biblioteca de clases para crear una red neuronal
6 NeuroNet.cl Biblioteca Biblioteca de código para el programa OpenCL

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

Archivos adjuntos |
MQL5.zip (3133.22 KB)
Вадим
Вадим | 5 oct 2025 en 08:47

¿Alguien podría decirme cómo solucionar esto?

«Math» no es una clase, estructura ni unión VAE.mqh 93 8

«MathRandomNormal»: se espera algún operador VAE.mqh 93 14

No se permite la conversión del tipo de parámetro «int[1]» a «const uint[] &» VAE.mqh 135 55

Número de parámetros incorrecto: se han pasado 4, pero se requieren 5 VAE.mqh 135 15

Podría ser una de estas dos funciones: VAE.mqh 135 15

bool COpenCL::Execute(const int, const int, const uint&[], const uint&[]) OpenCL.mqh 82 22

bool COpenCL::Execute(const int, const int, const uint&[], const uint&[], const uint&[]) OpenCL.mqh 83 22

???


Dmitriy Gizlyk
Dmitriy Gizlyk | 6 oct 2025 en 14:42
Вадим #:

¿Alguien podría decirme cómo solucionar esto? -

«Math» no es una clase, una estructura ni una unión VAE.mqh 93 8

«MathRandomNormal»: se espera algún operador VAE.mqh 93 14

No se permite la conversión de parámetros del tipo «int[1]» a «const uint[] &» VAE.mqh 135 55

Número de parámetros incorrecto: se han pasado 4, pero se requieren 5 VAE.mqh 135 15

Podría ser una de las dos funciones VAE.mqh 135 15

bool COpenCL::Execute(const int, const int, const uint&[], const uint&[]) OpenCL.mqh 82 22

bool COpenCL::Execute(const int, const int, const uint&[], const uint&[], const uint&[]) OpenCL.mqh 83 22

???


¿Utilizas todos los archivos de la biblioteca de este artículo o el archivo VAE.mqh de versiones anteriores?
Redes neuronales en el trading: señales de trading estables en cualquier régimen de mercado (Módulos de atención) Redes neuronales en el trading: señales de trading estables en cualquier régimen de mercado (Módulos de atención)
En este artículo continuaremos con la implementación de los enfoques del framework ST-Expert, centrándonos en los aspectos prácticos de su uso mediante MQL5. En artículos anteriores, hemos analizado los fundamentos teóricos y los componentes clave del modelo; ahora pasaremos a trabajar directamente con los algoritmos de atención de grafos y de distribución de atención local y global. El objetivo principal de este artículo es mostrar cómo las ideas conceptuales de ST-Expert se convierten en soluciones funcionales para el análisis y la previsión de series financieras.
Automatización del indicador del sentimiento del mercado Automatización del indicador del sentimiento del mercado
En este artículo, automatizaremos un indicador personalizado del sentimiento del mercado que clasifica las condiciones del mercado en alcistas, bajistas, de apetito por el riesgo, de aversión al riesgo y neutras. El asesor experto ofrece información en tiempo real sobre el sentimiento predominante, al tiempo que agiliza el proceso de análisis de las tendencias o la dirección actuales del mercado.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Creación de sistemas de trading basados en IA en MQL5 (Parte 2): Desarrollo de un programa integrado con ChatGPT y con interfaz de usuario Creación de sistemas de trading basados en IA en MQL5 (Parte 2): Desarrollo de un programa integrado con ChatGPT y con interfaz de usuario
En este artículo, desarrollaremos un programa en MQL5 integrado con ChatGPT y dotado de una interfaz de usuario, aprovechando el framework para el procesamiento de JSON de la primera parte para enviar prompts a la API de OpenAI y mostrar las respuestas en un gráfico de MetaTrader 5. Asismo, implementaremos un panel de control con un campo de entrada, un botón de envío y un área de visualización de la respuesta, gestionando la comunicación con la API y el ajuste de texto para la interacción con el usuario.