Redes neuronales en el trading: entrenamiento de metaparámetros basado en la heterogeneidad (HimNet)
Introducción
La aparición de una amplia gama de herramientas para recopilar datos en flujo ha dado lugar a la acumulación masiva de series espaciotemporales en ámbitos muy diversos de la actividad humana. En los sistemas de negociación se generan flujos de cotizaciones, órdenes y operaciones en distintas plataformas de negociación, tickers y tipos de instrumentos financieros. A medida que aumentan los volúmenes de datos, la predicción precisa de estas series se convierte en una herramienta para reducir costes, gestionar riesgos y mejorar la ejecución de las estrategias de negociación. Un error en la previsión de la liquidez local o de la volatilidad se traduce directamente en deslizamiento, pérdida de ganancias y distorsión de los indicadores de la estrategia de trading. Por eso, un modelo de calidad debe considerar no solo la tendencia global, sino también las diferencias locales y contextuales: dónde y cuándo el mercado es más rígido, y dónde es más laxo.
La principal dificultad radica en la heterogeneidad espaciotemporal de los datos. La heterogeneidad espacial del mercado se manifiesta cuando distintas plataformas de negociación o activos muestran patrones diferentes al mismo tiempo. Por ejemplo, algunas bolsas pueden tener una liquidez profunda, mientras que otras presentan brechas de arbitraje, sobre todo en períodos de alta volatilidad. En estas circunstancias, intentar promediar el comportamiento de todas las plataformas de negociación provoca el suavizado de efectos locales importantes y la pérdida de precisión.
La heterogeneidad temporal se manifiesta con mayor intensidad de lo que parece a primera vista. El comportamiento del mismo instrumento financiero cambia radicalmente según la hora del día y las distintas fechas del calendario. La apertura y el cierre de la sesión de negociación son dos ejemplos claros: en los primeros minutos tras la apertura se observa un repunte del volumen y la volatilidad, mientras que a última hora de la noche, por el contrario, la actividad es escasa. Las noticias y los anuncios macroeconómicos crean regímenes de volatilidad extrema a corto plazo. Los días festivos y los fines de semana provocan un aletargamiento de la actividad. En conjunto, esto significa que un mismo patrón no puede considerarse estacionario en el tiempo: sus estadísticas varían, y el modelo debe tenerlo en cuenta.
Los enfoques propuestos anteriormente intentaban resolver el problema de distintas maneras, pero cada uno de ellos presenta cuellos de botella desde el punto de vista de su aplicación al trading. Los métodos basados en grafos usaban la topología y métricas de similitud calculadas previamente entre puntos de datos; es como un trader que basa su estrategia exclusivamente en un mapa de liquidez trazado de antemano. En el mundo real, el mapa queda obsoleto rápidamente: cambia la composición de los creadores de mercado, aparecen nuevas plataformas de negociación y surgen brechas temporales de liquidez.
Los métodos de metaaprendizaje ofrecían la posibilidad de almacenar varios conjuntos de parámetros para distintos regímenes, pero a menudo requerían información externa. En finanzas, esto significa que se necesita metainformación accesible y correcta sobre cada plataforma de negociación y cada instrumento financiero. Pero no siempre se puede obtener en tiempo real. Además, muchos de estos enfoques son demasiado pesados en memoria y cómputo.
Los enfoques basados en el aprendizaje de representaciones (Representation Learning) ya permiten extraer embeddings informativos de datos brutos. Esto es potente, pero a menudo después se aplica un procesamiento simplificado de los embeddings, y el valor de una buena representación se pierde en el siguiente paso. El aprendizaje autosupervisado añade tareas auxiliares para que los embeddings sean más ricos; esto es útil, pero no siempre permite entrenar de forma óptima el modelo para la tarea objetivo de predicción en un único ciclo.
Precisamente por eso, los autores del trabajo «Heterogeneity-Informed Meta-Parameter Learning for Spatiotemporal Time Series Forecasting» propusieron un enfoque diferente: el framework HimNet. Su solución combina dos componentes clave: primero, la identificación implícita de la heterogeneidad a través de embeddings entrenables, y, a continuación, el uso de esta información para el metaaprendizaje de los parámetros del modelo. En pocas palabras, los creadores del framework no solo pretenden identificar las diferencias entre segmentos concretos de datos, sino que también entrenan al modelo para que seleccione y genere parámetros únicos para cada contexto.
Imagine que en el pool de información de mercado hay:
- las acciones del mayor emisor en la bolsa principal durante el horario de negociación;
- las mismas acciones en plataformas extrabursátiles tras un pico local de noticias;
- valores pequeños y poco líquidos;
- mercados de criptomonedas con distinta profundidad del libro de órdenes.
Los embeddings espaciales separarán estos grupos en clústeres independientes. A continuación, el sistema extrae del pool de metaparámetros conjuntos únicos de parámetros para cada clúster. Para el mercado principal: parámetros agresivos que tengan en cuenta el bajo deslizamiento, y para la negociación extrabursátil, parámetros conservadores que hagan la ejecución más robusta. Los valores pequeños y poco líquidos reciben parámetros con una regularización más fuerte para evitar sobreajustarse al ruido. Y para cripto, un conjunto que tiene en cuenta el alto nivel de ruido y las frecuentes brechas de arbitraje. Todo esto se entrena en un único ciclo, de modo que el modelo aprende al mismo tiempo a distinguir contextos y a seleccionar los mejores parámetros para cada uno de ellos.
Los ejemplos temporales son igual de importantes. Las horas de apertura matutinas suelen exigir una reacción rápida y pronósticos de volumen a corto plazo. Los periodos posteriores a la publicación de datos macroeconómicos clave corresponden a un régimen de mayor volatilidad. Las horas tardías corresponden a un régimen de baja actividad. El mecanismo de metaparámetros propuesto por los autores del framework permite asignar conjuntos de parámetros diferenciados para distintos regímenes temporales. Es como cuando un trader experimentado cambia su estilo de trading en función de la hora del día y del calendario.
Técnicamente, la idea clave consiste en la clusterización de los embeddings y la cuantización dinámica de los parámetros mediante un pool de metaparámetros pequeño y computacionalmente eficiente. Esto resuelve de una vez varios problemas de los enfoques anteriores. En primer lugar, no existe una dependencia rígida de características auxiliares externas: el modelo identifica por sí mismo los contextos relevantes. En segundo lugar, el pool de metaparámetros es compacto, por lo que los costes de memoria y recursos computacionales se mantienen controlados, lo cual es importante para los sistemas con baja latencia de ejecución. En tercer lugar, los autores del framework pasan de la detección pasiva de la heterogeneidad a su uso activo: los clústeres determinan directamente qué parámetros se aplican. Esto aumenta la precisión de los pronósticos y mejora la calidad de la ejecución.
La interpretabilidad merece una mención especial. Los metaparámetros y los clústeres se pueden visualizar: una visualización de clústeres mostrará qué plataformas de negociación y qué ventanas temporales quedan agrupadas en un mismo régimen; el mapa de calor de los metaparámetros mostrará en qué se diferencian los ajustes para los regímenes de baja y alta volatilidad. Para los profesionales, no se trata solo de un gráfico atractivo, sino también de un medio de control: si un clúster incluye inesperadamente plataformas de negociación que son distintas por su naturaleza, esto es una señal para realizar una comprobación adicional de los datos o para revisar el procedimiento de preprocesamiento.
Por último, la robustez y la transferibilidad. Una característica importante del framework HimNet es su flexibilidad entre dominios. Los metaparámetros y los embeddings se entrenan de tal forma que puedan aplicarse también a otros instrumentos financieros y plataformas de negociación con un ajuste fino mínimo.
En definitiva, el framework HimNet ofrece una vía práctica y eficaz para identificar regímenes espaciotemporales, forma un pool compacto de metaparámetros y entrena al modelo para utilizar la información obtenida. Esto proporciona a los traders y a los desarrolladores de sistemas de predicción una herramienta que, al mismo tiempo, mejora la precisión, mantiene la eficiencia computacional y aumenta la interpretabilidad.
Algoritmo HimNet
Un aspecto clave en la modelización de la heterogeneidad espaciotemporal es identificar y separar correctamente el contexto de los datos de origen en el tiempo y el espacio. En lugar de utilizar datos de referencia externos, los autores del framework HimNet crean embeddings entrenables que asignan una representación única a cada contexto espaciotemporal. Esto confiere al modelo la flexibilidad y la adaptabilidad necesarias para resolver problemas financieros reales.
Para la dimensión temporal, los autores del framework crean dos diccionarios de embeddings:
- diccionario de hora del díaDtod ∈ RNd * dtod;
- diccionario de días de la semanaDdow ∈ RNw * ddow.
Aquí, dtod y ddow son las dimensiones de los vectores correspondientes, Nd es el número de pasos dentro del día de negociación y Nw es el número de días de la semana.
Para un mini-batch de datos de entrada Xb ∈ RB*T*N con una longitud histórica T, los autores del framework proponen utilizar la marca de tiempo del último paso de cada muestra para seleccionar de los diccionarios los embeddings temporales Etod ∈ RB*dtod y Edow ∈ RB*ddow. A continuación, los combinan mediante concatenación en un embedding temporal común.
![]()
El sentido práctico de esta separación es sencillo. La hora del día capta la periodicidad con alta resolución: los repuntes matutinos en la apertura, la calma de la hora del almuerzo y la sesión vespertina. El día de la semana capta ciclos más largos: el comportamiento en días laborables y fines de semana, así como patrones recurrentes en torno a las fechas de publicación de resultados y los comunicados económicos. En conjunto, estos diccionarios permiten al modelo reconocer regímenes temporales en varias escalas y seleccionar para ellos los parámetros de predicción adecuados.
Para la dimensión espacial, los autores del framework utilizan una matriz de embeddings espaciales Es ∈ RN*ds, donde N es el número de series temporales o ubicaciones, y ds es la dimensión del vector para cada ubicación. A diferencia de los diccionarios temporales, aquí cada espacio está directamente asociado a su propio vector entrenable, inicializado de forma aleatoria. El objetivo es captar las diferencias funcionales entre las plataformas de negociación que influyen en los patrones de las series: la profundidad del libro de órdenes, la velocidad de ejecución, el perfil de los participantes, el tipo de instrumentos financieros y las características de la infraestructura. Esto permite tener en cuenta la heterogeneidad espacial sin recurrir explícitamente a características auxiliares.
Este tipo de entrenamiento de embeddings realiza, en esencia, un clustering dinámico. Durante el proceso de entrenamiento, las representaciones en las matrices se separan y se agrupan gradualmente en función de los datos de origen. Los embeddings correspondientes a contextos espaciotemporales con comportamientos similares se acercan en el espacio latente. Y las que tienen dinámicas diferentes divergen. Como resultado, se forman clústeres naturales: regímenes típicos de comportamiento. Esto se parece a los modelos de lenguaje, en los que rey y reina resultan cercanos semánticamente. Solo que aquí la proximidad refleja la similitud entre los regímenes de mercado. Es importante que este clustering se produzca de forma natural, sin restricciones externas estrictas ni etiquetado manual. El modelo aprende por sí mismo qué contextos son similares y cuáles no.
Veamos algunos ejemplos financieros más detallados. Para ilustrar todo con más precisión, consideraremos algunos contextos típicos:
- Apertura de la sesión bursátil principal. Alta actividad, grandes volúmenes, rápidas fluctuaciones de precios. Para estas combinaciones temporales y espaciales, el modelo identificará un embedding específico y seleccionará parámetros orientados a horizontes cortos y a una reacción rápida; esto es importante para los algoritmos de ejecución rápida y para reducir el deslizamiento.
- Negociación extrabursátil de los mismos valores durante el periodo nocturno. Volúmenes bajos, operaciones esporádicas, mayor riesgo de saltos bruscos por órdenes aisladas. En este caso, los embeddings temporales serán diferentes, y los metaparámetros harán que el modelo adopte un comportamiento más conservador, aumentando la robustez y reduciendo la probabilidad de sobreajuste al ruido.
- Plataformas de negociación de criptomonedas con distinta profundidad del libro de órdenes. Una bolsa tiene liquidez profunda; otra, liquidez escasa y propicia para el arbitraje. Los embeddings espaciales separarán estas plataformas, y el modelo aplicará diferentes estrategias de predicción de volúmenes y precio, lo cual resulta útil para los sistemas de arbitraje.
- Días de grandes publicaciones macroeconómicas o de informes trimestrales. Los embeddings temporales del día de la semana, junto con la hora del día, identificarán esas ventanas; el modelo pasará a un modo con mayor sensibilidad a las señales a corto plazo y con intervalos de confianza más amplios.
La idea clave es sencilla y, al mismo tiempo, eficaz. Nuestra tarea no consiste solo en registrar la heterogeneidad, sino también en aprender a gestionarla. Hacerla prácticamente útil para la predicción y la ejecución en los mercados. En lugar de intentar mantener un conjunto independiente de parámetros para cada minuto y para cada plataforma de negociación, lo cual resulta inaceptable tanto desde el punto de vista económico como técnico, los autores del framework amplían el espacio de parámetros del modelo a lo largo de las dimensiones temporal y espacial, y luego representan esta ampliación de forma compacta en forma de pequeños pools de metaparámetros. Para una consulta espaciotemporal concreta, los parámetros finales se generan como una combinación ponderada de los candidatos del pool: Θ = Q · P. Este esquema permite trabajar con un conjunto compacto de parámetros, en lugar de optimizar una enorme cantidad de parámetros únicos, lo que reduce drásticamente los requisitos de memoria y cálculo. En lugar de que el coste crezca con la escala de todos los pasos temporales y ubicaciones, pagamos por la adaptación con solo un número reducido de candidatos.
En la práctica, esto significa que mantenemos por separado un pequeño pool temporal, del que extraemos el embedding temporal Et, y obtenemos los metaparámetros temporales. De forma similar, el embedding espacial Es recurre al pool espacial y proporciona metaparámetros para cada secuencia individual. No hay que olvidar, además, las firmas espaciotemporales mixtas: construir directamente un pool conjunto completo resultaría ineficiente, por lo que los autores del framework codifican los datos de entrada Xt en un embedding espaciotemporal Est = Fenc(Xt) y lo utilizan como consulta a un pool relativamente pequeño para generar metaparámetros ST. Gracias a esta división, el modelo gana flexibilidad: es capaz de ajustar los parámetros a un momento concreto, a una plataforma de negociación concreta y a una combinación concreta y simultánea de eventos, sin perder por ello capacidad de control ni velocidad de funcionamiento.
Este esquema también ofrece ventajas prácticas para el negocio. El reducido tamaño de los pools hace que la generación de metaparámetros sea barata en términos de tiempo de respuesta, lo cual es fundamental en tareas con requisitos estrictos de latencia. El entrenamiento conjunto de los pools y del modelo principal permite que los gradientes pasen a través de las consultas a los pools, de modo que los embeddings y los parámetros se optimicen de forma coordinada. Al mismo tiempo, es necesario vigilar la estabilidad del entrenamiento y controlar la distribución de los pesos de las consultas mediante la limitación de la entropía o el suavizado por temperatura, para evitar que un candidato acapare todo el tráfico y dé lugar a un modelo excesivamente complejo y con poca capacidad de generalización.
La elección del tamaño del pool es un compromiso entre una adaptabilidad pronunciada y el riesgo de sobreajuste; en la práctica, se ajusta mediante validación, tomando como referencia el número de regímenes típicos observados en los mercados objetivo.
La arquitectura del framework HimNet se basa en una combinación de convoluciones de grafos y reglas recurrentes: estos bloques son capaces de captar simultáneamente el historial de los datos de entrada y tener en cuenta las relaciones entre series individuales. Imagine a un trader que observa al mismo tiempo la cinta de operaciones y el mapa de interrelaciones entre los mercados, y que toma sus decisiones basándose en ambas perspectivas. Esa es precisamente la función que desempeña la celda básica del modelo. Agrega las señales vecinas según el grafo y actualiza el estado oculto teniendo en cuenta el tiempo.
Desde el punto de vista de la implementación técnica, el bloque básico es una Graph Convolutional Recurrent Unit (GCRU) modificada. En el interior: las habituales compuertas reset y update, el estado candidato y la mezcla de la representación oculta anterior y la nueva. Pero hay un detalle importante: los parámetros de la convolución de grafos no son fijos en este caso. Se generan dinámicamente mediante metamecanismos. Esto significa que los patrones de pesos para la agregación de vecinos (cómo una secuencia influye en otra) varían en función del contexto. Para el mercado financiero, esto es crítico: las interrelaciones son elásticas. Durante periodos de reacciones masivas en una gran bolsa, estas se estrechan. En otro intervalo de tiempo, cuando una plataforma de negociación queda temporalmente fuera de negociación, estas se debilitan.
El modelo se construye según el esquema clásico Codificador-Decodificador. El Codificador del framework HimNet funciona en dos flujos paralelos, que posteriormente se suman. Un flujo se adapta según el tiempo. Recibe embeddings temporales y, a partir de ellos, genera metaparámetros temporales. Otro flujo se adapta espacialmente: utiliza embeddings espaciales de las secuencias y genera metaparámetros para cada punto. Este enfoque de dos canales permite que el modelo tenga en cuenta al mismo tiempo lo que está ocurriendo en este momento y dónde está ocurriendo. Pero los creadores del framework no se quedan ahí. Para tener en cuenta los efectos espaciotemporales conjuntos, el Decodificador recibe un estado latente y lo proyecta en un embedding espaciotemporal que sirve como consulta al pool de metaparámetros ST. El resultado es un conjunto de pesos recurrentes específicos para la combinación actual de tiempo, lugar y dinámica.
La matriz de adyacencia adaptativa es otro elemento importante. En lugar de mantener una matriz de conexiones estática, el modelo la genera a partir de embeddings espaciales: los productos escalares de los embeddings pasan por ReLU y SoftMax. El resultado es una matriz que refleja las interrelaciones actuales del mercado. El efecto práctico de esto es que, cuando se produce un pico en una bolsa, el modelo refuerza automáticamente la influencia de dicha bolsa sobre los nodos vecinos. Cuando la liquidez se retira de alguna plataforma de negociación, su influencia se debilita. Esto hace que el comportamiento del modelo sea sensible al estado real del mercado, y no a un mapa estático que queda rápidamente obsoleto.
El decodificador de HimNet genera de forma iterativa predicciones para los siguientes pasos. Toma un estado oculto inicial, aplica una celda GCRU con metaparámetros ST y genera una predicción para cada paso futuro. Este enfoque no solo permite generar una predicción promediada para todas las secuencias, sino que genera predicciones que tienen en cuenta los regímenes locales y las ventanas temporales.
El entrenamiento se organiza como end-to-end. Los autores del framework utilizan una métrica sencilla pero clara: el MAE en todos los pasos y plataformas de negociación. Esto resulta práctico: la pérdida se mide en las mismas unidades que la magnitud pronosticada y resulta más fácil de interpretar para el negocio. Durante el entrenamiento, es importante controlar la estabilidad: resulta útil aplicar regularización L2 a los pools de metaparámetros, suavizado por temperatura en la generación de pesos y limitación de la tasa de actualización. El tamaño de los pools se elige buscando un equilibrio:
- demasiado pequeño: el modelo no abarcará todos los regímenes;
- si es demasiado grande, aparecerá el riesgo de una especialización excesiva y aumentará la carga computacional.
Desde el punto de vista de su aplicación práctica, HimNet aporta beneficios tangibles. Una evaluación local precisa de la liquidez reduce el deslizamiento en la ejecución de grandes bloques. HimNet proporciona a los sistemas de arbitraje un contexto para filtrar falsas brechas de arbitraje e identificar discrepancias persistentes entre las bolsas.
Por último, los detalles técnicos hacen que el sistema sea apto para el despliegue industrial. La generación de metaparámetros consiste en multiplicar matrices pequeñas, lo que no provoca un crecimiento explosivo del consumo de memoria. Los pools son compactos y permiten mantener una baja latencia, algo imprescindible para tareas con requisitos temporales estrictos. Todo ello facilita la integración de HimNet en el proceso de ejecución de órdenes y en el sistema de control de riesgos.
A continuación se muestra una visualización del framework HimNet realizada por el autor.

Implementación con MQL5
Tras un análisis detallado de los fundamentos teóricos del framework HimNet, el siguiente paso lógico es mostrar cómo plasmar estas ideas en código MQL5 funcional. La implementación práctica no consiste en una mera transposición de fórmulas, sino en traducir la arquitectura a las limitaciones y posibilidades de una plataforma de negociación real. Actuaremos de forma sistemática y meticulosa: dividiremos la gran arquitectura en objetos independientes y bien descritos, y construiremos el modelo paso a paso, como un maestro relojero ensambla un cronómetro: con detalle, de forma predecible y respetando cada engranaje. Este enfoque facilita las pruebas, agiliza la depuración y permite escalar la solución sin dificultad a distintos mercados y timeframes.
A nivel de la infraestructura de MQL5, esto significa que los bloques de preprocesamiento, embeddings, metaparámetros y celdas recurrentes existen como componentes independientes. Las secuencias de velas y de valores de los indicadores analizados se convierten en ventanas de datos bien estructuradas de longitud fija. Los embeddings de tiempo y de secuencias individuales se envían a los módulos de generación de pesos. La celda GCRU recibe como entrada la estructura de grafo del mercado y devuelve el estado oculto actualizado. Todo suena coherente, pero hay un elemento sin el cual HimNet pierde eficacia: la capacidad de captar la información de los nodos vecinos del grafo de forma rápida y estable a una distancia de varias aristas. En términos financieros, se trata de detectar no solo las influencias directas entre las secuencias, sino también los efectos de segundo y tercer orden, cuando un repunte de liquidez en un nodo se propaga por la red y llega al instrumento financiero analizado con retraso y atenuación.
Es precisamente aquí donde entra en escena el polinomio de Chebyshev. Permite construir un filtro K-hop local sobre un grafo sin necesidad de una descomposición espectral costosa. En lugar de calcular los vectores propios, los autores del framework utilizan una recursión sencilla que, a partir de la matriz de conexiones, genera una base formada por las matrices T0, T1, …, TK-1. Cada una de estas matrices corresponde a una capa de vecindad: desde el propio nodo hasta la vecindad de orden K. Como resultado, una operación de convolución se convierte en una combinación lineal limpia de varias transformaciones preparadas de antemano. Para el trading, esto es crítico: menor latencia, menor presión sobre la memoria y funcionamiento numérico estable en series largas y con valores grandes de N. Cuando el mercado se vuelve especialmente ruidoso, este filtro ayuda a identificar relaciones estables y a no reaccionar ante picos aleatorios.
Para que esta idea funcione en producción, necesitamos un objeto independiente que, a partir de la matriz de adyacencia actual, construya rápidamente las matrices del polinomio de Chebyshev del orden deseado. Se convertirá en el motor silencioso de toda la capa de grafos: recibe como entrada un support adaptativo, construido a partir de embeddings, y devuelve un conjunto compacto Tk, listo para su uso en GCRU.
El primer paso de nuestro trabajo práctico consiste en introducir cambios en el programa OpenCL. La GPU es ideal para esta tarea: el paralelismo multihilo y las operaciones vectoriales permiten procesar grandes matrices de adyacencia mucho más rápido que en la CPU. Esto resulta especialmente crítico para los datos financieros, donde el tamaño del grafo puede ser bastante grande y presentar conexiones ramificadas.
El algoritmo del kernel de pasada directa ChebStep es, en cierto modo, el corazón del mecanismo de generación de polinomios de Chebyshev que funciona directamente en las entrañas de OpenCL. Y se parece mucho al funcionamiento coordinado de un motor bursátil, donde cada tick y cada orden se entrelazan en una lógica unificada.
__kernel void ChebStep(__global const float* support, __global float* outputs, const int step ) { const size_t l = get_local_id(0); const size_t r = get_global_id(1); const size_t c = get_global_id(2); const size_t total_l = get_local_size(0); const size_t total_r = get_global_size(1); const size_t total_c = get_global_size(2);
El programa comienza por obtener las coordenadas en el espacio de tareas. Los identificadores locales y globales de hilos son nuestros traders bursátiles, cada uno de los cuales se encarga de una parte concreta de la matriz. Funcionan en paralelo para que todo el conjunto de datos se procese no de forma secuencial, como en el antiguo trading manual, sino de forma sincrónica, como en un sistema automatizado de trading de alta frecuencia. El búfer local Temp actúa como un centro de compensación temporal: aquí se acumulan las sumas intermedias, que posteriormente se sincronizan y forman el resultado final.
__local float Temp[LOCAL_ARRAY_SIZE]; //--- if(step <= 0 || total_r != total_c) return;
El algoritmo impone de inmediato una restricción importante: si el paso es menor o igual que cero, o si la matriz no es cuadrada, no tiene sentido continuar con los cálculos. Este es precisamente el caso en el que el trader rechaza una operación si las condiciones del mercado no se ajustan a las reglas de la estrategia.
A continuación, comienza la construcción secuencial de la jerarquía de órdenes de los polinomios de Chebyshev. El primer paso es muy sencillo: se crea una matriz identidad, en la que la diagonal se rellena con unos. Es como un capital inicial: la base desde la que todo comienza.
if(step <= 3) { const float diag = (r == c ? 1.0f : 0.0f); if(l == 0) outputs[RCtoFlat(r, c, total_r, total_c, 0)] = diag;
En el segundo paso entra en juego la matriz de adyacencia inicial, el mapa de conexiones entre los vértices del grafo que refleja la estructura real del mercado. Se añade al array de salida, y ya aquí comienza la aproximación al movimiento real de las cotizaciones.
if(step < 2) return; if(l == 0) { const float s = IsNaNOrInf(support[RCtoFlat(r, c, total_r, total_c, 0)], 0); outputs[RCtoFlat(r, c, total_r, total_c, 1)] = s; }
En el tercer paso se forma la segunda potencia: el cuadrado de la matriz. El código multiplica filas por columnas en un bucle, sumando cuidadosamente los productos. Es similar al análisis de correlaciones cruzadas entre activos, en el que buscamos no solo una dependencia directa, sino también relaciones indirectas a través de eslabones intermedios.
La matriz resultante refleja interacciones más complejas, en las que la influencia no se limita a las relaciones directas, sino que se propaga a través de los vecinos. Tras la suma, se aplican la normalización y el ajuste para eliminar la redundancia, del mismo modo que un trader filtra el ruido de las cotizaciones y deja solo las señales significativas.
if(step < 3) return; float out = 0; for(int t = 0; t < total_c; t += total_l) { const float s1 = IsNaNOrInf(support[RCtoFlat(r, t + l, total_r, total_c, 0)], 0); const float s2 = IsNaNOrInf(support[RCtoFlat(t + l, c, total_r, total_c, 0)], 0); out += IsNaNOrInf(s1 * s2, 0); } out = 2 * LocalSum(out, 0, Temp); if(l == 0) { out -= diag; outputs[RCtoFlat(r, c, total_r, total_c, 2)] = IsNaNOrInf(out, 0); } return; }
Si el paso es mayor que tres, el algoritmo pasa al nivel de la fórmula de recurrencia general de los polinomios de Chebyshev. Aquí, cada nueva matriz no se construye desde cero, sino a partir de una combinación de las potencias anteriores. El código toma la matriz de adyacencia, la multiplica por el resultado del paso anterior y la corrige restándole una matriz aún más anterior. Este proceso se asemeja a la construcción de indicadores de trading complejos: por ejemplo, las medias móviles exponenciales tienen en cuenta el estado previo, suavizan las fluctuaciones bruscas y, al mismo tiempo, no olvidan la historia.
float out = 0; for(int t = 0; t < total_c; t += total_l) { if((t + l) >= total_c) continue; const float s1 = IsNaNOrInf(support[RCtoFlat(r, t + l, total_r, total_c, 0)], 0); const float s2 = IsNaNOrInf(outputs[RCtoFlat(t + l, c, total_r, total_c, step - 2)], 0); out += IsNaNOrInf(s1 * s2, 0); } out = 2 * LocalSum(out, 0, Temp); if(l == 0) { out -= IsNaNOrInf(outputs[RCtoFlat(r, c, total_r, total_c, step - 3)], 0); outputs[RCtoFlat(r, c, total_r, total_c, step - 1)] = IsNaNOrInf(out, 0); } return; }
De este modo, el modelo va construyendo, paso a paso, aproximaciones cada vez más profundas de las propiedades espectrales del grafo.
Cada iteración en el kernel finaliza con la sincronización de los datos y una comprobación de corrección. En el código se utiliza de forma activa la función de protección contra NaN y los valores infinitos, lo que nos recuerda que el mercado no perdona los errores. Del mismo modo que la gestión de riesgos controla los límites de las pérdidas, aquí cualquier anomalía se elimina en una fase temprana para no distorsionar el resultado global.
Como resultado de esta lógica paso a paso, obtenemos toda una escalera de polinomios de Chebyshev, que se convierten en el material de construcción de los filtros espectrales en las redes neuronales de grafos. En la práctica, esto significa que el algoritmo no se limita a observar el mercado a través del prisma de las relaciones actuales, sino que construye una óptica multicapa que permite captar tanto dependencias locales como dependencias lejanas entre instrumentos financieros. Es algo parecido a cómo un analista experimentado es capaz de ver en la evolución del oro una señal indirecta para el mercado de divisas, o en la dinámica de los bonos, un indicio de cambios en las acciones.
Ahora que hemos analizado el algoritmo de construcción directa de los polinomios de Chebyshev y hemos visto cómo se construye, paso a paso, su estructura computacional, es lógico pasar al mecanismo de retropropagación del error. Aquí entra en juego un kernel especial, que se encarga de distribuir con precisión los gradientes por los polinomios de Chebyshev y hasta la matriz de adyacencia, garantizando el entrenamiento correcto de todo el modelo.
__kernel void ChebStepGrad(__global const float* support, __global float* support_g, __global const float* outputs, __global float* outputs_g, const int step ) { const size_t l = get_local_id(0); const size_t r = get_global_id(1); const size_t c = get_global_id(2); const size_t total_l = get_local_size(0); const size_t total_r = get_global_size(1); const size_t total_c = get_global_size(2); //--- __local float Temp[LOCAL_ARRAY_SIZE]; //--- if(step < 1 || total_r!=total_c) return;
Al inicio de la ejecución, el algoritmo también inicializa los identificadores locales y globales que, al igual que las cotizaciones bursátiles, determinan la posición de cada unidad de cálculo en una enorme malla de hilos paralelos. Se reserva memoria local para un array temporal con el fin de acelerar las reducciones, y las primeras comprobaciones garantizan que el paso sea correcto y que la matriz tenga forma cuadrada. Del mismo modo que un trader nunca analizaría un libro de órdenes incompleto, aquí el programa detiene inmediatamente su ejecución ante condiciones incorrectas.
Cuando el paso de obtención de los gradientes del error es mayor o igual que 2, empieza lo más interesante. En primer lugar, se toma el gradiente del paso siguiente y se resta directamente del nivel anterior de las salidas. Este momento se puede comparar con la corrección de una previsión: si en el último ciclo la estrategia produjo un error, su sombra se proyecta sobre el anterior.
if(step >= 2) { float grad = IsNaNOrInf(outputs_g[RCtoFlat(r, c, total_r, total_c, step)], 0); if(l == 0) outputs_g[RCtoFlat(r, c, total_r, total_c, step - 2)] -= grad;
A continuación, el algoritmo calcula los gradientes con respecto a la matriz de adyacencia. Cada hilo toma su fragmento de datos y multiplica el error de las salidas por los valores correspondientes del paso anterior, sumándolo todo cuidadosamente mediante la memoria local. El resultado es una señal correctiva que se asemeja al ajuste fino de las ponderaciones en una cartera: cuando algunos activos están sobreponderados, su influencia se redistribuye gradualmente.
//--- gradiente de support grad = 0; for(int t = 0; t < total_c; t += total_l) { if((t + l) >= total_c) continue; const float s2 = IsNaNOrInf(outputs[RCtoFlat(c, t + l, total_r, total_c, step - 2)], 0); grad += IsNaNOrInf(outputs_g[RCtoFlat(r, t + l, total_r, total_c, step)] * s2, 0); } grad = LocalSum(grad, 0, Temp); if(l == 0) outputs_g[RCtoFlat(r, c, total_r, total_c, 1)] += grad; BarrierLoc;
Una vez sincronizados los hilos, llega el momento de pasar a la siguiente parte: el cálculo del gradiente con respecto al polinomio Tk-1. Aquí todo funciona de forma análoga: cada hilo toma una parte de la matriz de adyacencia, la multiplica por los errores del siguiente paso y añade cuidadosamente su contribución a la suma total. Como resultado, se genera una corrección para los gradientes de los polinomios en el nivel k-1. Este proceso es similar a cuando un analista retrocede un paso en su modelo y comprueba si se ha sobrevalorado el riesgo, para luego aplicar el ajuste correspondiente.
//--- gradiente de T(k-1) grad = 0; for(int t = 0; t < total_c; t += total_l) { if((t + l) >= total_c) continue; const float s2 = IsNaNOrInf(support[RCtoFlat(t + l, r, total_r, total_c, 0)], 0); grad += IsNaNOrInf(outputs_g[RCtoFlat(t + l, c, total_r, total_c, step)] * s2, 0); } grad = LocalSum(grad, 0, Temp); if(l == 0) outputs_g[RCtoFlat(r, c, total_r, total_c, step - 1)] += grad; }
Tenga en cuenta que recopilamos cuidadosamente todos los gradientes del error en un tensor correspondiente a los polinomios de Chebyshev. En ese mismo tensor también se incluyen los gradientes intermedios de la matriz de adyacencia, que se calculan en cada paso de la pasada inversa. Es importante recordar que la propia matriz de adyacencia se almacena dentro de un polinomio de primer orden, por lo que es precisamente este nivel el que se convierte en el punto clave de intersección de los flujos de datos. Y solo al llegar a este punto trasladamos el gradiente de error acumulado al búfer de la matriz de adyacencia, como si fijáramos el saldo final tras una serie de correcciones intermedias.
De este modo, este kernel asume el papel de una especie de «revisor» de los cálculos, que evita que el error se pierda o se diluya en el camino hacia atrás. Cada valor pasa por un sistema de sumas locales, se ajusta y se envía allí donde pueda modificar los pesos en favor de una predicción más precisa. Y al igual que en el mercado, donde cada décima adicional de punto porcentual puede resultar decisiva, aquí también la distribución precisa del gradiente por pasos permite que el modelo se entrene de forma estable y no se desvíe de su rumbo.
Ahora que ya tenemos preparada la base (la generación de matrices y la distribución de gradientes), surge una pregunta lógica: ¿quién se encargará de gestionar todo este proceso desde el programa principal? Se necesita un objeto que asuma el papel de una especie de despachador, que encapsule cuidadosamente los kernels de OpenCL de bajo nivel y ofrezca una interfaz cómoda para interactuar con otros módulos del modelo. Así surge la clase CChebPolinom, que hereda las interfaces básicas del objeto de capa completamente conectada CNeuronBaseOCL.
class CChebPolinom : public CNeuronBaseOCL { protected: uint iDimension; uint iSteps; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { return true; } virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CChebPolinom(void) {}; ~CChebPolinom(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint steps, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual bool Save(const int file_handle) override; virtual bool Load(const int file_handle) override; //--- virtual int Type(void) override const { return defChebPolinom; } virtual uint GetDimension(void) const { return iDimension; } virtual uint GetSteps(void) const { return iSteps; } };
En su interior almacena los parámetros clave: la dimensión del espacio y el número de pasos necesarios para la descomposición en polinomios de Chebyshev. Estos parámetros determinan los límites de los cálculos y la profundidad que el modelo puede alcanzar al trabajar con la estructura de grafo.
La inicialización del objeto se lleva a cabo en el método Init, que permite configurar todos los parámetros necesarios.
bool CChebPolinom::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint steps, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, dimension * dimension * steps, optimization_type, batch)) return false; //--- iDimension = dimension; iSteps = steps; //--- return true; }
El algoritmo del método es bastante sencillo. En primer lugar, cedemos el control al método homónimo de la clase padre, indicando una dimensión del objeto suficiente para almacenar el tensor concatenado de todos los polinomios de Chebyshev necesarios. A continuación, el objeto almacena en su interior dos parámetros principales: la dimensión iDimension y el número de pasos iSteps. Estos valores se convierten en una especie de «parte identificativa» de la clase, en la que se basarán todos los demás métodos. Se podría decir que aquí es donde configuramos definitivamente el espacio de trabajo: establecemos tanto el ancho del lienzo como el número de capas por las que pasará la información.
El retorno de true al final del método confirma que el objeto se ha preparado correctamente para funcionar. Y aunque la implementación parezca concisa, es precisamente en esta fase donde se resuelve una cuestión fundamental: si nuestro algoritmo será capaz de expandir eficazmente la aproximación polinómica en la GPU.
El método de pasada directa feedForward, en esencia, actúa como un despachador que se limita a encapsular y organizar la llamada al kernel correspondiente del programa de OpenCL. Su estructura es muy reconocible: comprobación de los datos de entrada, preparación de los parámetros de funcionamiento y llamada secuencial al kernel. Sin embargo, la especificidad del problema —la generación y aplicación de polinomios de Chebyshev— le aporta un toque especial.
bool CChebPolinom::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL || NeuronOCL.Neurons() != (iDimension * iDimension)) return false;
En primer lugar, el método comprueba que se haya recibido un objeto válido como entrada y que el número de neuronas coincida con el tamaño de la matriz de adyacencia iDimension * iDimension. Si no se cumple esta condición, seguir trabajando simplemente no tiene sentido.
A continuación, se preparan los rangos de trabajo: las dimensiones globales de la malla de cálculo se determinan de modo que tengan en cuenta la limitación real de la GPU en cuanto al tamaño del grupo local y que reflejen la estructura matricial del problema; las tres dimensiones, en esencia, distribuyen la malla de cálculo a lo largo de los ejes de la matriz.
uint global_work_offset[3] = { 0 }; uint global_work_size[3] = { MathMin(iDimension, uint(OpenCL.GetMaxLocalSize(0))), iDimension, iDimension }; uint local_work_size[3] = { global_work_size[0], 1, 1 };
El elemento clave aquí es el bucle que recorre los pasos. Es este bucle el que refleja la naturaleza recursiva de la construcción de los polinomios de Chebyshev: para obtener el valor del polinomio en el nivel k, es necesario basarse en los valores anteriores. A partir del paso mínimo válido —normalmente el segundo, ya que los dos primeros vienen determinados por las condiciones iniciales—, el método pasa sucesivamente al kernel el número del paso y, a continuación, inicia la ejecución. De este modo, en cada iteración, la GPU recibe una nueva tarea: construir el siguiente nivel de aproximación polinómica.
//--- uint kernel = def_k_ChebStep; setBuffer(kernel, def_k_cheb_support, NeuronOCL.getOutputIndex()) setBuffer(kernel, def_k_cheb_outputs, getOutputIndex()) for(int step =::MathMin(2, MathMax(int(iSteps) - 1, 0)); step < int(iSteps); step++) { setArgument(kernel, def_k_cheb_step, step + 1) kernelExecuteLoc(kernel, global_work_offset, global_work_size, local_work_size) } //--- return true; }
El resultado es una especie de matrioshka computacional: cada paso depende del anterior, pero, al mismo tiempo, todo el trabajo se paraleliza por elementos de la matriz. En los mercados financieros, este enfoque recuerda a una estrategia de construcción de pronósticos basada en horizontes móviles: cada nueva predicción no se elabora desde cero, sino como continuación del anterior, precisando y profundizando el panorama.
Ahí radica precisamente la fuerza de este método: lacónico en su forma, combina la sencillez de la lógica del despachador con una profunda recursividad matemática.
El método encargado de distribuir los gradientes del error se basa en un principio similar. Al igual que en el caso del paso directo, la tarea principal de este método consiste en organizar correctamente las llamadas al kernel correspondiente del programa OpenCL, teniendo en cuenta la estructura recursiva de los cálculos de los polinomios de Chebyshev. Todos los gradientes acumulados se suman cuidadosamente y se distribuyen entre los pasos correspondientes, prestando especial atención a la matriz de adyacencia y a los polinomios intermedios.
Se puede realizar por cuenta propia un análisis detallado de las líneas de este método. El código fuente completo de la clase, incluidos todos sus métodos, se incluye en el archivo adjunto, lo que permite formarse una idea global del funcionamiento de esta parte del framework.
La cantidad de material ya es considerable, y ahora es el momento ideal para hacer una pequeña pausa y sistematizar y ordenar toda la información obtenida. Un desglose detallado por apartados permitirá asimilar mejor los principios de funcionamiento y las relaciones entre los componentes. En el próximo artículo abordaremos la continuación del desarrollo de los algoritmos del framework HimNet, incluida la integración práctica de todos los módulos.
Conclusión
En este artículo nos hemos familiarizado con los aspectos teóricos del framework HimNet y hemos pasado a la implementación práctica de los enfoques propuestos con MQL5 y OpenCL. Hemos analizado en detalle el concepto de los metaparámetros espaciotemporales. Hemos analizado cómo los bloques recurrentes convolucionales de grafos son capaces de tener en cuenta las dependencias temporales y espaciales, y también hemos examinado los algoritmos de generación y aplicación de polinomios de Chebyshev en la GPU para acelerar los cálculos y mejorar la robustez del modelo.
En el próximo artículo continuaremos con el desarrollo de los algoritmos del framework HimNet.
Referencias
-
Heterogeneity-Informed Meta-Parameter Learning for Spatiotemporal Time Series Forecasting
- Otros artículos de la serie
Programas utilizados en el artículo
| # | Nombre | Tipo | Descripción |
|---|---|---|---|
| 1 | Study.mq5 | Asesor experto | Asesor experto para el entrenamiento offline de modelos |
| 2 | StudyOnline.mq5 | Asesor experto | Asesor experto para el entrenamiento online de modelos |
| 3 | Test.mq5 | Asesor experto | Asesor experto para probar el modelo |
| 4 | Trajectory.mqh | Biblioteca de la clase | Estructura de descripción del estado del sistema y de la arquitectura de los modelos |
| 5 | NeuroNet.mqh | Biblioteca de la clase | Biblioteca de clases para crear una red neuronal |
| 6 | NeuroNet.cl | Biblioteca | Biblioteca de código del programa OpenCL |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/19233
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Análisis de la dinámica horaria de los símbolos de trading y de sus spreads en MetaTrader 5
Herramientas de trading de MQL5 (Parte 6): Panel de control holográfico dinámico con animaciones de pulso y controles interactivos
Particularidades del trabajo con números del tipo double en MQL4
Entrenamiento de un U-Transformer no lineal sobre los residuos de un modelo autorregresivo lineal
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso