English Русский Português
preview
Entrenamiento de un U-Transformer no lineal sobre los residuos de un modelo autorregresivo lineal

Entrenamiento de un U-Transformer no lineal sobre los residuos de un modelo autorregresivo lineal

MetaTrader 5Asesores Expertos |
17 1
Yevgeniy Koshtenko
Yevgeniy Koshtenko

En el trading algorítmico actual, los desarrolladores se enfrentan a un dilema fundamental: los modelos lineales son sencillos y fáciles de interpretar, pero son incapaces de captar las complejas relaciones no lineales que se dan en las series temporales financieras. Las redes neuronales profundas, por el contrario, pueden modelar teóricamente cualquier no linealidad, pero sufren de sobreajuste e inestabilidad en condiciones de elevado ruido de mercado y cantidades limitadas de datos.

Este artículo presenta un enfoque híbrido innovador que aborda este dilema mediante un modelado en dos etapas: en primer lugar, un modelo autorregresivo lineal con 25 características extrae las principales regularidades estadísticas de los datos de precios; a continuación, una arquitectura U-Transformer especializada se entrena con los residuos del modelo lineal, identificando patrones no lineales ocultos que la primera etapa no pudo captar.

La innovación clave consiste en adaptar la arquitectura U-Net (creada inicialmente para la segmentación de imágenes) mediante la integración de bloques Transformer para el análisis de series temporales. El sistema se ha implementado en MQL5 e incluye una lógica de negociación completa con gestión dinámica de posiciones, reoptimización automática de parámetros y entrenamiento en línea de la red neuronal.

La validación experimental demuestra la superioridad del enfoque híbrido frente a los métodos puramente lineales, al tiempo que se mantiene la eficiencia computacional y la interpretabilidad de los resultados. El sistema presentado es capaz de funcionar en tiempo real, adaptándose automáticamente a las condiciones cambiantes del mercado.



Introducción

Los mercados financieros son sistemas complejos y adaptativos en los que los métodos tradicionales de predicción se enfrentan a una serie de retos fundamentales. El primero y más evidente de ellos es el problema de la no linealidad. Los datos del mercado muestran patrones complejos que no pueden describirse mediante simples relaciones lineales: cambios de régimen en la volatilidad, reacciones asimétricas ante las noticias y efectos en cadena relacionados con la liquidez. Sin embargo, los intentos de aplicar potentes modelos no lineales de aprendizaje automático a los datos financieros suelen dar lugar a un segundo problema: el sobreajuste y la inestabilidad de las predicciones.

Este problema se manifiesta de forma especialmente aguda en el trading algorítmico, donde el modelo no solo debe predecir con precisión la dirección del movimiento del precio, sino también hacerlo de forma estable en distintos regímenes de mercado, sin dejar de ser computacionalmente eficiente para funcionar en tiempo real. Los modelos econométricos clásicos, como los de tipo ARIMA o VAR, ofrecen estabilidad e interpretabilidad, pero su naturaleza lineal impone estrictas limitaciones a la capacidad de modelar la compleja dinámica del mercado.

Por otro lado, las arquitecturas actuales de aprendizaje profundo —LSTM, GRU, Transformer— son, en teoría, capaces de aproximar cualquier relación no lineal, pero su aplicación a los datos financieros se ve complicada por varios factores. En primer lugar, el elevado nivel de ruido en los datos de mercado hace que las redes neuronales empiecen a «memorizar» fluctuaciones aleatorias en lugar de aprender las verdaderas regularidades. En segundo lugar, la no estacionariedad de las series financieras implica que los patrones aprendidos a partir de datos históricos pueden perder rápidamente su relevancia.

El tercer problema es de carácter práctico: las limitaciones computacionales de las plataformas de trading, especialmente en el lenguaje MQL5, no permiten aprovechar plenamente los frameworks modernos de aprendizaje profundo. El desarrollador se ve obligado a implementar algoritmos de redes neuronales «desde cero», lo que limita considerablemente la complejidad de las arquitecturas aplicables.

La solución propuesta en este trabajo se basa en la idea de descomponer el problema de la predicción en dos etapas de naturaleza diferente: una etapa lineal para captar las principales regularidades estadísticas y una etapa no lineal para modelar dependencias residuales complejas. Este enfoque permite aprovechar los puntos fuertes de ambas clases de métodos, minimizando sus desventajas.

El modelo autorregresivo lineal de la primera etapa se basa en un espacio cuidadosamente diseñado compuesto por 25 características, que incluyen no solo los retardos de precios clásicos, sino también sus transformaciones no lineales, indicadores técnicos y componentes cíclicos. Este modelo se optimiza mediante descenso por gradiente y ofrece un nivel básico de calidad en las predicciones con una alta interpretabilidad.

El componente de red neuronal de la segunda etapa utiliza una arquitectura U-Transformer adaptada, que combina los principios de U-Net (estructura de codificador-decodificador con skip-connections) y los mecanismos de atención de la arquitectura Transformer. La característica clave de este enfoque reside en que la red neuronal no se entrena a partir de los datos de precios originales, sino a partir de los residuos del modelo lineal, lo que simplifica considerablemente la tarea y reduce el riesgo de sobreajuste.

Esta descomposición proporciona una regularización natural: si las condiciones del mercado cambian y el componente de red neuronal empieza a ofrecer predicciones inestables, el sistema pasa automáticamente a utilizar las predicciones más conservadoras del modelo lineal. Esto se consigue mediante un esquema de ponderación adaptativo, en el que el peso del componente de red neuronal depende de la calidad actual de ambos modelos.



Fundamentos teóricos de la arquitectura U-Transformer

La arquitectura U-Transformer constituye una síntesis de dos potentes conceptos de la visión por computadora y el procesamiento del lenguaje natural: U-Net y los mecanismos de atención (attention). En un principio, U-Net se desarrolló para tareas de segmentación de imágenes médicas, en las que se requiere una localización precisa de los objetos manteniendo el contexto global. La idea clave de U-Net radica en una arquitectura simétrica de codificador-decodificador con skip-connections horizontales, que permiten transmitir información detallada desde las capas iniciales directamente a las capas finales del decodificador.

En el contexto del análisis de series temporales, esta arquitectura adquiere un nuevo significado. El codificador comprime la información temporal de forma secuencial, extrayendo patrones cada vez más abstractos, mientras que el decodificador reconstruye la resolución temporal utilizando tanto representaciones abstractas como características detalladas a través de skip-connections. Esto es especialmente importante en el caso de los datos financieros, en los que las fluctuaciones locales pueden ser tan significativas como las tendencias globales.

struct UTransformerNet
{
    NeuralLayer encoder_layers[3];     // Codificador: compresión de la información
    NeuralLayer decoder_layers[3];     // Decodificador: restauración de la resolución
    AttentionBlock attention_heads[4]; // atención multi-cabeza
    double skip_connections[3][32];    // Conexiones horizontales de U-Net
    double residuals[6000];            // Residuos para el entrenamiento
    double neural_predictions[6000];   // Predicciones de la red neuronal
};

El mecanismo de autoatención, tomado de la arquitectura Transformer, añade al modelo la capacidad de centrarse dinámicamente en las partes más relevantes de la secuencia de entrada. A diferencia de las capas convolucionales o recurrentes, la atención permite al modelo relacionar directamente acontecimientos separados en el tiempo, lo cual es de vital importancia para los mercados financieros, donde el impacto de los acontecimientos puede manifestarse con retraso.

void SelfAttention(double &inputs[], AttentionBlock &attention, double &outputs[])
{
    double queries[32], keys[32], values[32];
    
    // Cálculo de las transformaciones Q, K y V
    for (int i = 0; i < NeuralNodes; i++)
    {
        queries[i] = 0; keys[i] = 0; values[i] = 0;
        for (int j = 0; j < NeuralNodes; j++)
        {
            queries[i] += inputs[j] * attention.query_weights[j][i];
            keys[i] += inputs[j] * attention.key_weights[j][i];
            values[i] += inputs[j] * attention.value_weights[j][i];
        }
    }
    
    // Cálculo de las puntuaciones de atención: Q × K^T
    for (int i = 0; i < NeuralNodes; i++)
    {
        attention.attention_scores[i] = 0;
        for (int j = 0; j < NeuralNodes; j++)
        {
            attention.attention_scores[i] += queries[i] * keys[j];
        }
        attention.attention_scores[i] /= MathSqrt(NeuralNodes); // Escalado
    }
}

La diferencia clave entre la arquitectura propuesta y los modelos Transformer clásicos radica en su adaptación a series temporales unidimensionales, en lugar de a secuencias bidimensionales de tokens. En lugar de la codificación posicional, se utilizan características temporales (hora del día, componentes cíclicos), lo que permite al modelo tener en cuenta la estacionalidad intradía de las sesiones de trading.



Modelo híbrido: regresión lineal más red neuronal

La idea central del enfoque híbrido consiste en la descomposición aditiva de la predicción en dos componentes de naturaleza diferente:

Predicción_final = Modelo_lineal(X) + α × U_Transformer(Residuos)

donde α es un coeficiente de ponderación adaptativo que depende de la calidad actual de ambos modelos.

La componente lineal se construye como un modelo autorregresivo clásico con un espacio de características ampliado:

// Cálculo de la predicción lineal
double linear_pred = 0;
for (int i = 0; i < 25; i++)
    linear_pred += g_pair.coeffs[i] * features[i];
La componente de red neuronal no se entrena con los datos de precios originales, sino con los residuos del modelo lineal:
void TrainUTransformer()
{
    // Cálculo de los residuos del modelo lineal
    for (int i = 0; i < g_pair.data_size; i++)
    {
        double linear_pred = 0;
        for (int j = 0; j < 25; j++)
            linear_pred += g_pair.coeffs[j] * g_pair.features[i][0][j];
        
        g_pair.neural_net.residuals[i] = g_pair.prices[i] - linear_pred;
    }
    
    // Entrenamiento de la red neuronal sobre los residuos
    for (int epoch = 0; epoch < NeuralEpochs; epoch++)
    {
        double total_loss = 0;
        for (int i = 0; i < g_pair.data_size; i++)
        {
            double prediction = UTransformerForward(g_pair.coeffs, 
                                                   g_pair.neural_net.residuals[i]);
            double error = prediction - g_pair.neural_net.residuals[i];
            total_loss += error * error;
        }
    }
}

Esta descomposición ofrece varias ventajas críticas. En primer lugar, la red neuronal resuelve una tarea mucho más sencilla: modelar los residuos en lugar de la serie original. Los residuos suelen presentar una varianza menor y propiedades estadísticas más estacionarias, lo que simplifica el entrenamiento y reduce el riesgo de sobreajuste.

En segundo lugar, el sistema implementa de forma natural el principio «fail-safe»: si la componente de red neuronal empieza a generar predicciones inestables (un valor de loss elevado), el sistema aumenta automáticamente el peso de la componente lineal:

double GetHybridPrediction(double price_t1, double price_t2, double price_t3)
{
    // Predicción lineal
    double linear_pred = 0;
    for (int i = 0; i < 25; i++)
        linear_pred += g_pair.coeffs[i] * features[i];
    
    // Corrección de la red neuronal
    double neural_correction = 0;
    if (g_pair.neural_net.is_trained)
    {
        neural_correction = UTransformerForward(g_pair.coeffs, 0);
    }
    
    // Ponderación adaptativa
    double confidence = MathMin(g_pair.current_r2, 0.8);
    double neural_weight = g_pair.neural_net.is_trained ? (1.0 - confidence) : 0.0;
    
    return linear_pred + neural_weight * neural_correction;
}

El coeficiente «neural_weight» se adapta automáticamente a la calidad del modelo: cuando el R² de la componente lineal es alto (buena calidad), el aporte de la red neuronal recibe un peso menor; cuando el R² es bajo, recibe un peso mayor. Esto garantiza la estabilidad del sistema en distintos regímenes de mercado y evita situaciones en las que una red neuronal mal entrenada deteriora predicciones lineales de calidad.



Espacio multidimensional de características e ingeniería de variables

La calidad de cualquier modelo de aprendizaje automático depende de manera crítica de la informatividad de las variables de entrada. En la predicción financiera, esta dependencia se manifiesta con especial intensidad, ya que los datos de mercado se caracterizan por un alto nivel de ruido y por su no estacionariedad. El sistema propuesto utiliza un espacio de características de 25 dimensiones cuidadosamente diseñado, que incluye tanto retardos de precios clásicos como sus transformaciones no lineales, indicadores técnicos y componentes cíclicos.

El conjunto básico de características incluye términos directos y cuadráticos de los retardos de precios:

void CalculateFeatures(double price_t1, double price_t2, double price_t3, 
                      int bar_index, string symbol, double &features[])
{
    // Componentes lineales del precio
    features[0] = price_t1;                    // X(t-1)
    features[1] = MathPow(price_t1, 2);        // X(t-1)²
    features[2] = price_t2;                    // X(t-2)
    features[3] = MathPow(price_t2, 2);        // X(t-2)²
    features[4] = price_t3;                    // X(t-3)
    
    // Operadores de diferencias (momentum)
    features[5] = (price_t1 - price_t2);      // Momentum a corto plazo
    features[9] = (price_t1 - price_t3);      // Momentum a medio plazo
    features[18] = MathPow(price_t1 - price_t2, 2); // Momentum cuadrático
}
Los componentes cíclicos modelan patrones periódicos en distintas escalas temporales:
// Ciclos de baja frecuencia (estacionalidad diaria)
    features[6] = MathSin(price_t1);
    features[7] = MathCos(price_t1);
    
    // Ciclos de alta frecuencia (patrones intradía)
    features[13] = MathSin(price_t1 * 1000);
    features[14] = MathCos(price_t1 * 1000);
    
    // Componente temporal (hora de la sesión de negociación)
    datetime bar_time = iTime(symbol, PERIOD_H1, bar_index);
    MqlDateTime dt;
    TimeToStruct(bar_time, dt);
    features[17] = (dt.hour / 24.0);

Las transformaciones no lineales ayudan al modelo a adaptarse a distintos regímenes de volatilidad:

// Transformaciones de raíz y exponenciales
    features[15] = MathSqrt(MathAbs(price_t1));
    features[16] = MathExp(-MathAbs(price_t1 - price_t2));
    
    // Tangente hiperbólica para limitar los valores atípicos
    features[23] = MathTanh(price_t1 - ma);

Los indicadores técnicos aportan información sobre la microestructura del mercado:

// El RSI y sus versiones no lineales
    double rsi = 50.0; // Cálculo simplificado a modo de ejemplo
    features[10] = (rsi / 100.0);
    features[21] = MathPow(rsi / 100.0, 2);
    
    // Desviación respecto a la media móvil
    double ma = price_t1; // Cálculo simplificado
    features[11] = (price_t1 - ma);
    
    // Volatilidad (ATR)
    double atr = MathAbs(price_t1 - price_t2);
    features[12] = atr;
    features[20] = (atr * (price_t1 - price_t2)); // Interacción entre volatilidad y momentum

Una categoría especial la constituyen las características de interacción, que modelan efectos no lineales entre distintos componentes:

// Interacciones por pares
    features[8] = (price_t1 * price_t2);
    features[19] = (price_t1 / price_t2);
    
    // Interacciones triples
    features[22] = (price_t1 * price_t2 * price_t3);
    
    // Término constante
    features[24] = 1.0;

Esta diversidad de características permite que el modelo lineal capture un amplio espectro de regularidades del mercado, desde movimientos tendenciales simples hasta patrones no lineales complejos. Al mismo tiempo, el componente de red neuronal se entrena con los residuos de este modelo lineal enriquecido, lo que simplifica considerablemente su tarea.



Estructuras de datos y organización de la memoria en MQL5

La implementación de algoritmos complejos de aprendizaje automático en el entorno MQL5 requiere prestar especial atención a la organización de la memoria y de las estructuras de datos. A diferencia de los frameworks modernos de aprendizaje profundo, que gestionan automáticamente la memoria y los grafos computacionales, en MQL5 el desarrollador debe diseñar manualmente estructuras de datos eficaces.

La estructura central del sistema, PairData, organiza todos los datos necesarios para un par de divisas:

struct PairData
{
    string analyst_symbol;    // Símbolo para el análisis (EURUSD)
    string trade_symbol;      // Símbolo para operar (USDJPY)
    
    // Coeficientes del modelo lineal
    double coeffs[25];        // Coeficientes actuales
    double best_coeffs[25];   // Los mejores coeficientes encontrados
    
    // Métricas de calidad
    double current_r2;        // R² actual
    double best_r2;           // Mejor R² alcanzado
    double learning_rate;     // Tasa de aprendizaje adaptativa
    
    // Series temporales y características
    double prices[6000];                    // Precios objetivo
    double features[6000][50][25];          // Secuencias de características
    int data_size;                          // Tamaño actual de los datos
    
    // Estado de las posiciones para averaging (promediación de posiciones)/pyramiding (piramidación de posiciones)
    double last_buy_price;    // Precio de la última posición BUY
    double last_sell_price;   // Precio de la última posición SELL
    int buy_levels;           // Número de niveles BUY
    int sell_levels;          // Número de niveles SELL
    
    // Componente de red neuronal
    UTransformerNet neural_net;
};

La estructura de la capa neuronal está optimizada para una multiplicación matricial eficiente:

struct NeuralLayer
{
    double weights[64][64];   // Matriz de pesos de la capa
    double biases[64];        // Vector de sesgos
    double outputs[64];       // Salidas de las neuronas
    double gradients[64];     // Gradientes para la retropropagación
    int size;                 // Tamaño real de la capa
};

El bloque de atención implementa una versión simplificada de la atención multi-cabeza:

struct AttentionBlock
{
    double query_weights[32][32];  // Matriz de transformación de consultas
    double key_weights[32][32];    // Matriz de transformación de claves  
    double value_weights[32][32];  // Matriz de transformación de valores
    double attention_scores[32];   // Pesos de atención
    double context[32];            // Vector contextual
};

Una característica clave de la organización de la memoria es el uso de arrays estáticos de tamaño fijo en lugar de estructuras dinámicas. Esto se debe a la necesidad de garantizar un consumo predecible de memoria:

// Los tamaños máximos de los arrays se definen como constantes
double prices[6000];                    // Un máximo de 6000 puntos históricos
double features[6000][50][25];          // Secuencias de 50 barras
double residuals[6000];                 // Residuos para el entrenamiento de la red neuronal

El array tridimensional features[6000][50][25] merece especial atención. La primera dimensión corresponde a los puntos de datos históricos; la segunda, a secuencias temporales de longitud 50 (para su posible uso en mecanismos recurrentes o de atención); y la tercera, a 25 características. Esta organización garantiza un acceso eficaz a los datos tanto durante el entrenamiento como durante la predicción.

La inicialización de los pesos de la red neuronal emplea la inicialización Xavier/Glorot para garantizar un entrenamiento estable:

void InitializeNeuralNetwork()
{
    MathSrand(GetTickCount());
    
    for (int layer = 0; layer < NeuralLayers; layer++)
    {
        int input_size = (layer == 0) ? 25 : NeuralNodes;
        double scale = MathSqrt(2.0 / (input_size + NeuralNodes));
        
        for (int i = 0; i < input_size; i++)
        {
            for (int j = 0; j < NeuralNodes; j++)
            {
                g_pair.neural_net.encoder_layers[layer].weights[i][j] =
                    (MathRand() / 32767.0 - 0.5) * 2.0 * scale;
            }
        }
    }
}

Esta organización de la memoria garantiza un funcionamiento eficaz del sistema incluso con recursos limitados de la plataforma de trading, al tiempo que conserva la posibilidad de implementar algoritmos complejos de aprendizaje automático.



Algoritmo de entrenamiento del sistema de dos componentes

El entrenamiento de un sistema híbrido es un proceso iterativo en el que los componentes lineal y neuronal se optimizan de forma sucesiva, pero coordinada. Este enfoque garantiza una convergencia estable y permite que cada componente se especialice en su parte del problema de predicción.

Primera etapa: optimización del modelo lineal mediante descenso por gradiente con tasa de aprendizaje adaptativa:

void OptimizeCoefficients()
{
    double best_coeffs[25];
    ArrayCopy(best_coeffs, g_pair.coeffs);
    double best_r2 = CalculateR2();
    
    g_pair.learning_rate = InitialLearningRate;
    
    for (int iter = 0; iter < MaxIterations; iter++)
    {
        double gradients[25];
        ArrayInitialize(gradients, 0.0);
        
        // Cálculo de los gradientes para todos los ejemplos de entrenamiento
        for (int i = 0; i < g_pair.data_size; i++)
        {
            double actual = g_pair.prices[i];
            double predicted = 0.0;
            
            // Pasada directa del modelo lineal
            for (int j = 0; j < 25; j++)
                predicted += g_pair.coeffs[j] * g_pair.features[i][0][j];
            
            double error = predicted - actual;
            
            // Acumulación de gradientes: ∂L/∂w_j = 2 * error * x_j
            for (int j = 0; j < 25; j++)
                gradients[j] += 2.0 * error * g_pair.features[i][0][j];
        }
        
        // Normalización de los gradientes
        for (int j = 0; j < 25; j++)
            gradients[j] /= g_pair.data_size;

Un elemento de vital importancia es el recorte de gradientes para prevenir los gradientes explosivos:

// Recorte de gradientes para garantizar la estabilidad
        double gradient_norm = 0.0;
        for (int j = 0; j < 25; j++)
            gradient_norm += gradients[j] * gradients[j];
        gradient_norm = MathSqrt(gradient_norm);
        
        if (gradient_norm > 1.0)
        {
            for (int j = 0; j < 25; j++)
                gradients[j] /= gradient_norm;
        }
        
        // Actualización de los coeficientes
        for (int j = 0; j < 25; j++)
            g_pair.coeffs[j] -= g_pair.learning_rate * gradients[j];

La tasa de aprendizaje adaptativa se ajusta automáticamente a la dinámica de la optimización:

double new_r2 = CalculateR2();
        
        if (new_r2 > best_r2)
        {
            // Se ha encontrado una mejora: aumentamos la tasa de aprendizaje
            best_r2 = new_r2;
            ArrayCopy(best_coeffs, g_pair.coeffs);
            g_pair.learning_rate *= 1.01;
        }
        else
        {
            // Deterioro: revertimos y reducimos la tasa de aprendizaje
            ArrayCopy(g_pair.coeffs, best_coeffs);
            g_pair.learning_rate *= 0.8;
            
            if (g_pair.learning_rate < InitialLearningRate * 0.01)
                break; // Tasa de aprendizaje demasiado baja: nos detenemos
        }
    }
}

Segunda etapa: entrenamiento de la red neuronal con los residuos del modelo lineal optimizado:

void TrainUTransformer()
{
    // Cálculo de los residuos tras la optimización del modelo lineal
    for (int i = 0; i < g_pair.data_size; i++)
    {
        double linear_pred = 0;
        for (int j = 0; j < 25; j++)
            linear_pred += g_pair.coeffs[j] * g_pair.features[i][0][j];
        
        g_pair.neural_net.residuals[i] = g_pair.prices[i] - linear_pred;
    }
    
    double best_loss = 1e6;
    int no_improve_count = 0;
    
    for (int epoch = 0; epoch < NeuralEpochs; epoch++)
    {
        double total_loss = 0;
        
        for (int i = 0; i < g_pair.data_size; i++)
        {
            // Pasada directa de la red neuronal
            double prediction = UTransformerForward(g_pair.coeffs, 
                                                   g_pair.neural_net.residuals[i]);
            
            // Cálculo de la pérdida MSE
            double error = prediction - g_pair.neural_net.residuals[i];
            total_loss += error * error;
            
            // Retropropagación simplificada (gradiente respecto a la última capa)
            double gradient = 2.0 * error / g_pair.data_size;
            
            for (int layer = NeuralLayers - 1; layer >= 0; layer--)
            {
                for (int j = 0; j < NeuralNodes; j++)
                {
                    for (int k = 0; k < NeuralNodes; k++)
                    {
                        g_pair.neural_net.encoder_layers[layer].weights[k][j] -=
                            g_pair.neural_net.learning_rate * gradient * 0.01;
                    }
                }
            }
        }
        
        total_loss /= g_pair.data_size;
        
        // Early stopping para evitar el sobreajuste
        if (total_loss < best_loss)
        {
            best_loss = total_loss;
            no_improve_count = 0;
        }
        else
        {
            no_improve_count++;
            if (no_improve_count > 5) break;
        }
    }
}

La coordinación del entrenamiento de los dos componentes se lleva a cabo mediante una reoptimización periódica:

if (g_pair.bars_since_optimization >= OptimizationInterval)
{
    PrepareOptimizationData();
    OptimizeCoefficients();        // Primero, el modelo lineal
    
    // Reentrenamiento de la red neuronal cada 5 ciclos de optimización
    if (g_pair.neural_net.training_steps % 5 == 0)
    {
        TrainUTransformer();       // A continuación, la red neuronal sobre los nuevos residuos
    }
    
    g_pair.bars_since_optimization = 0;
}



Procedimiento de pasada directa a través del U-Transformer

La pasada directa a través del U-Transformer consiste en el procesamiento secuencial de los datos de entrada mediante una arquitectura codificador-decodificador con mecanismos de atención integrados. El procedimiento comienza con la preparación del vector de entrada, que incluye las características promediadas a lo largo de la secuencia temporal:

double UTransformerForward(double &coefficients[], double residual)
{
    double layer_input[32];
    double layer_output[32];
    double attention_output[32];
    
    // Preparación de los datos de entrada: promediado de las características a lo largo de la secuencia
    double avg_features[25];
    for (int i = 0; i < 25; i++)
    {
        avg_features[i] = 0;
        for (int j = 0; j < 50; j++)
        {
            avg_features[i] += g_pair.features[0][j][i];
        }
        avg_features[i] /= 50;
    }
    
    // Inicialización de la capa de entrada
    for (int i = 0; i < 25; i++)
        layer_input[i] = avg_features[i];

La parte codificadora de la arquitectura procesa la información de forma secuencial a través de varias capas, cada una de las cuales se especializa en la extracción de características de distintos niveles de abstracción:

// Recorrido por las capas codificadoras conservando las skip-connections
    for (int layer = 0; layer < NeuralLayers; layer++)
    {
        int input_size = (layer == 0) ? 25 : NeuralNodes;
        
        // Pasada directa a través de una capa totalmente conectada
        ForwardLayer(layer_input, input_size, 
                    g_pair.neural_net.encoder_layers[layer], layer_output);
        
        // Aplicación de la autoatención (para las primeras capas)
        if (layer < TransformerHeads)
        {
            SelfAttention(layer_output, 
                         g_pair.neural_net.attention_heads[layer], 
                         attention_output);
            
            // Conexión residual: salida = capa + atención
            for (int i = 0; i < NeuralNodes; i++)
                layer_output[i] = layer_output[i] + attention_output[i];
        }
        
        // Conservación de la skip-connection para el decodificador
        for (int i = 0; i < NeuralNodes; i++)
            g_pair.neural_net.skip_connections[layer][i] = layer_output[i];
        
        // Preparación de la entrada para la siguiente capa
        for (int i = 0; i < NeuralNodes; i++)
            layer_input[i] = layer_output[i];
    }

La capa totalmente conectada realiza una operación estándar de transformación lineal con activación no lineal:

void ForwardLayer(double &inputs[], int input_size, NeuralLayer &layer, double &outputs[])
{
    for (int j = 0; j < layer.size; j++)
    {
        double sum = layer.biases[j];
        
        // Multiplicación matricial: W * x + b
        for (int i = 0; i < input_size; i++)
            sum += inputs[i] * layer.weights[i][j];
        
        // Activación GELU para una mejor convergencia
        outputs[j] = GELU(sum);
    }
}

GELU (Gaussian Error Linear Unit) se utiliza como alternativa moderna a ReLU:

double GELU(double x) 
{ 
    return 0.5 * x * (1.0 + Tanh(MathSqrt(2.0 / M_PI) * (x + 0.044715 * x * x * x))); 
}

El mecanismo de autoatención calcula los pesos de atención para las distintas partes de la secuencia de entrada:

void SelfAttention(double &inputs[], AttentionBlock &attention, double &outputs[])
{
    double queries[32], keys[32], values[32];
    
    // Cálculo de los vectores Query, Key y Value
    for (int i = 0; i < NeuralNodes; i++)
    {
        queries[i] = 0; keys[i] = 0; values[i] = 0;
        for (int j = 0; j < NeuralNodes; j++)
        {
            queries[i] += inputs[j] * attention.query_weights[j][i];
            keys[i] += inputs[j] * attention.key_weights[j][i];
            values[i] += inputs[j] * attention.value_weights[j][i];
        }
    }
    
    // Normalización softmax de los pesos de atención
    double max_score = attention.attention_scores[0];
    for (int i = 1; i < NeuralNodes; i++)
        if (attention.attention_scores[i] > max_score)
            max_score = attention.attention_scores[i];
    
    double sum_exp = 0;
    for (int i = 0; i < NeuralNodes; i++)
    {
        attention.attention_scores[i] = MathExp(attention.attention_scores[i] - max_score);
        sum_exp += attention.attention_scores[i];
    }
    
    for (int i = 0; i < NeuralNodes; i++)
        attention.attention_scores[i] /= sum_exp;
    
    // Aplicación de los pesos de atención a los valores
    for (int i = 0; i < NeuralNodes; i++)
    {
        outputs[i] = 0;
        for (int j = 0; j < NeuralNodes; j++)
            outputs[i] += attention.attention_scores[j] * values[j];
    }
}

La agregación final utiliza pooling promedio para obtener una salida escalar:

// Parte decodificadora (simplificada: solo utiliza la última salida del codificador)
    double final_sum = 0;
    for (int i = 0; i < NeuralNodes; i++)
        final_sum += layer_output[i];
    
    return final_sum / NeuralNodes; // Pooling promedio
}

Esta arquitectura permite un procesamiento de la información en varios niveles: las capas de bajo nivel extraen patrones locales, los mecanismos de atención modelan las dependencias a largo plazo y las skip-connections conservan información detallada para la predicción final.



Sistema de generación de señales de trading

La generación de señales de trading en el sistema híbrido implementa una arquitectura de dos niveles con conmutación automática entre las fuentes de señales en función de la calidad de cada componente. Esta arquitectura garantiza la estabilidad del sistema ante diferentes regímenes de mercado y evita la degradación del rendimiento cuando se deteriora la calidad de uno de los modelos.

La fuente primaria de señales es el U-Transformer, que se activa cuando se alcanza una calidad de entrenamiento suficiente:

void ProcessPair()
{
    // Obtención de datos para el análisis a partir del símbolo analizado
    double price_t1 = iClose(g_pair.analyst_symbol, PERIOD_H1, 1);
    double price_t2 = iClose(g_pair.analyst_symbol, PERIOD_H1, 2);
    double price_t3 = iClose(g_pair.analyst_symbol, PERIOD_H1, 3);
    
    // Precio actual del instrumento operado
    double current_ask = SymbolInfoDouble(g_pair.trade_symbol, SYMBOL_ASK);
    double current_bid = SymbolInfoDouble(g_pair.trade_symbol, SYMBOL_BID);
    double current_price = (current_ask + current_bid) / 2.0;
    
    int signal = 0;
    
    // SEÑAL PRINCIPAL: U-Transformer (con alta calidad)
    if (g_pair.neural_net.is_trained && g_pair.neural_net.loss < 0.01)
    {
        double features[25];
        CalculateFeatures(price_t1, price_t2, price_t3, 1, g_pair.analyst_symbol, features);
        double neural_prediction = UTransformerForward(g_pair.coeffs, 0);
        
        double neural_threshold = 0.0001;
        
        if (neural_prediction > neural_threshold) signal = 1;      // Señal BUY
        else if (neural_prediction < -neural_threshold) signal = -1; // Señal SELL
    }
}

La fuente de señales de reserva se activa cuando la red neuronal aún no está entrenada o muestra resultados insatisfactorios:

// SEÑAL DE RESERVA: Modelo lineal
    if (signal == 0 && g_pair.current_r2 > 0.1)
    {
        double predicted_price = GetHybridPrediction(price_t1, price_t2, price_t3);
        double base_threshold = g_pair.current_r2 * 0.001;
        
        if (predicted_price > current_ask + base_threshold) signal = 1;
        if (predicted_price < current_bid - base_threshold) signal = -1;
    }

Un elemento de vital importancia es el umbral de activación adaptativo, que se escala en función de la calidad del modelo. Para las señales de la red neuronal se utiliza un umbral fijo, ya que el modelo se entrena con residuos normalizados. En el caso de las señales lineales, el umbral es proporcional al R² del modelo: cuanto mayor sea la calidad del ajuste, más agresivas serán las señales que pueda generar el sistema.

El registro de la fuente de la señal garantiza la transparencia de las decisiones de trading:

if (OpenPosition(true, lot))
{
    Print(g_pair.trade_symbol, ": BUY opened by ",
          g_pair.neural_net.is_trained ? "U-TRANSFORMER" : "LINEAR",
          " R2=", DoubleToString(g_pair.current_r2, 3),
          " UT_Loss=", DoubleToString(g_pair.neural_net.loss, 6));
}

La separación de símbolos para el análisis y el trading permite utilizar la práctica de operar con símbolos sintéticos (por ejemplo, barras Renko). Por ejemplo, el análisis de EURUSD Renko puede proporcionar señales para operar con EURUSD, lo que amplía las posibilidades del sistema y mejora potencialmente la calidad de las señales gracias al uso de información adicional.



Estrategias de gestión de posiciones y gestión de riesgos

El sistema de gestión de posiciones aplica dos estrategias complementarias: averaging (promediación de posiciones perdedoras) y pyramiding (piramidación de posiciones ganadoras). Estas estrategias se adaptan automáticamente a las condiciones del mercado y están integradas en el sistema de generación de señales.

La estructura de seguimiento de posiciones incluye todos los parámetros necesarios para poner en práctica estrategias complejas:

struct PairData
{
    // Seguimiento del estado de las posiciones
    double last_buy_price;      // Precio de la última posición BUY
    double last_sell_price;     // Precio de la última posición SELL
    int buy_levels;             // Número de niveles de posiciones BUY
    int sell_levels;            // Número de niveles de posiciones SELL
    bool last_was_averaging;    // Indicador de la última operación de averaging (promediación de posiciones)
};

La estrategia de averaging (promediación de posiciones) se activa cuando el precio se mueve en sentido contrario a la posición abierta una distancia predeterminada y, al mismo tiempo, el sistema sigue generando señales en la misma dirección:

// AVERAGING (PROMEDIACIÓN DE POSICIONES) DE POSICIONES BUY
if (g_pair.last_buy_price > 0)
{
    double distance_points = (g_pair.last_buy_price - current_price) / 
                            SymbolInfoDouble(g_pair.trade_symbol, SYMBOL_POINT);
    
    if (EnableAveraging && distance_points >= DistancePoints && signal == 1 &&
        g_pair.buy_levels < MaxAveragingLevels)
    {
        double lot = NormalizeLot(g_pair.trade_symbol, LotSize * AveragingLotMultiplier);
        if (OpenPosition(true, lot))
        {
            g_pair.last_buy_price = current_price;
            g_pair.buy_levels++;
            g_pair.last_was_averaging = true;
            
            Print(g_pair.trade_symbol, ": BUY AVERAGING level ", g_pair.buy_levels,
                  " at distance ", DoubleToString(distance_points, 1), " points");
        }
    }
}

La estrategia de pyramiding (piramidación de posiciones) funciona de forma opuesta: añade a las posiciones rentables:

// PYRAMIDING (PIRAMIDACIÓN DE POSICIONES) DE POSICIONES BUY
distance_points = (current_price - g_pair.last_buy_price) / 
                  SymbolInfoDouble(g_pair.trade_symbol, SYMBOL_POINT);

if (EnablePyramiding && distance_points >= DistancePoints && signal == 1 &&
    g_pair.buy_levels < MaxPyramidingLevels && !g_pair.last_was_averaging)
{
    double lot = NormalizeLot(g_pair.trade_symbol, LotSize * PyramidingLotMultiplier);
    if (OpenPosition(true, lot))
    {
        g_pair.last_buy_price = current_price;
        g_pair.buy_levels++;
        
        Print(g_pair.trade_symbol, ": BUY PYRAMIDING level ", g_pair.buy_levels,
              " at distance ", DoubleToString(distance_points, 1), " points");
    }
}

Una característica clave de la implementación es la prohibición del uso simultáneo de averaging (promediación de posiciones) y pyramiding (piramidación de posiciones) (indicador last_was_averaging), lo que evita que la estrategia degenere en una incorporación caótica de posiciones.

La gestión del tamaño de los lotes utiliza multiplicadores para distintos tipos de operaciones:

double NormalizeLot(string symbol, double lot)
{
    double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
    double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
    double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
    
    lot = MathRound(lot / lot_step) * lot_step;
    lot = MathMax(min_lot, MathMin(max_lot, lot));
    
    return lot;
}

Esta arquitectura de gestión de posiciones ofrece flexibilidad a la hora de gestionar las posiciones, al tiempo que permite mantener el control sobre el riesgo máximo. El sistema puede adaptarse a diversas condiciones de mercado utilizando averaging (promediación de posiciones) en mercados tendenciales y pyramiding (piramidación de posiciones) en movimientos impulsivos, manteniendo siempre una salida clara al alcanzar beneficios.


Reoptimización automática de los coeficientes del modelo

La adaptabilidad a las condiciones cambiantes del mercado es un requisito crítico para cualquier sistema de trading. Los mercados financieros muestran un comportamiento no estacionario, en el que los patrones estadísticos pueden variar bajo la influencia de eventos macroeconómicos, cambios en la microestructura del mercado o cambios en el comportamiento de los participantes. El sistema implementa una reoptimización automática que actualiza periódicamente los parámetros del modelo sobre la base de una ventana deslizante de datos históricos.

El desencadenante de la reoptimización es la acumulación de un número determinado de nuevas barras de trading:

void OnTick()
{
    if (!g_initialized) return;
    
    ProcessPair();
    
    // Comprobación de la necesidad de reoptimización
    if (isNewBar())
    {
        if (g_pair.bars_since_optimization >= OptimizationInterval)
        {
            PrepareOptimizationData();      // Preparación de los datos
            OptimizeCoefficients();         // Reoptimización del modelo lineal
            
            // Reentrenamiento de la red neuronal cada 5 ciclos
            if (g_pair.neural_net.training_steps % 5 == 0)
            {
                TrainUTransformer();
            }
            
            g_pair.bars_since_optimization = 0;
        }
    }
}

La preparación de datos para la optimización utiliza una ventana deslizante de tamaño fijo, lo que garantiza un equilibrio entre la estabilidad del modelo y la adaptabilidad a los cambios:

void PrepareOptimizationData()
{
    g_pair.data_size = 0;
    
    int available_bars = iBars(g_pair.analyst_symbol, PERIOD_H1);
    if (available_bars < 50)
    {
        Print("WARNING: Insufficient bars for ", g_pair.analyst_symbol);
        return;
    }
    
    int max_data_points = MathMin(OptimizationBars, 6000);
    
    for (int i = 50; i < MathMin(max_data_points + 50, available_bars - 1); i++)
    {
        if (g_pair.data_size >= 6000) break;
        
        double price_t0 = iClose(g_pair.analyst_symbol, PERIOD_H1, i - 1);
        if (price_t0 <= 0) continue;
        
        g_pair.prices[g_pair.data_size] = price_t0;
        
        // Recopilación de secuencias temporales de características
        for (int j = 0; j < 50; j++)
        {
            double price_t1 = iClose(g_pair.analyst_symbol, PERIOD_H1, i - j);
            double price_t2 = iClose(g_pair.analyst_symbol, PERIOD_H1, i - j - 1);
            double price_t3 = iClose(g_pair.analyst_symbol, PERIOD_H1, i - j - 2);
            
            if (price_t1 <= 0 || price_t2 <= 0 || price_t3 <= 0) continue;
            
            double features[25];
            CalculateFeatures(price_t1, price_t2, price_t3, i - j, 
                            g_pair.analyst_symbol, features);
            
            for (int k = 0; k < 25; k++)
            {
                g_pair.features[g_pair.data_size][j][k] = features[k];
            }
        }
        
        g_pair.data_size++;
    }
    
    Print(g_pair.analyst_symbol, ": Prepared ", g_pair.data_size, " data points");
}

El procedimiento de optimización incluye un mecanismo para conservar los mejores coeficientes encontrados:

void OptimizeCoefficients()
{
    if (g_pair.data_size < 10)
    {
        Print(g_pair.analyst_symbol, ": Insufficient data (", g_pair.data_size, ")");
        return;
    }
    
    double best_coeffs[25];
    ArrayCopy(best_coeffs, g_pair.coeffs);
    double best_r2 = CalculateR2();
    
    // Criterio de mejora para continuar con la optimización
    double initial_r2 = g_pair.current_r2;
    
    for (int iter = 0; iter < MaxIterations; iter++)
    {
        // ... código de descenso de gradiente ...
        
        double new_r2 = CalculateR2();
        
        if (new_r2 > best_r2)
        {
            best_r2 = new_r2;
            ArrayCopy(best_coeffs, g_pair.coeffs);
            g_pair.learning_rate *= 1.01; // Aceleración cuando hay mejora
        }
        else
        {
            ArrayCopy(g_pair.coeffs, best_coeffs);
            g_pair.learning_rate *= 0.8;  // Desaceleración cuando hay empeoramiento
            
            if (g_pair.learning_rate < InitialLearningRate * 0.01)
                break;
        }
        
        // Criterio de parada por mejora mínima
        if (iter > 10 && (best_r2 - initial_r2) < MinR2Improvement)
            break;
    }
    
    // Actualización de los mejores coeficientes
    ArrayCopy(g_pair.coeffs, best_coeffs);
    g_pair.current_r2 = best_r2;
    
    if (best_r2 > g_pair.best_r2)
    {
        g_pair.best_r2 = best_r2;
        ArrayCopy(g_pair.best_coeffs, best_coeffs);
    }
    
    Print(g_pair.analyst_symbol, ": R2=", DoubleToString(g_pair.current_r2, 4),
          " Best=", DoubleToString(g_pair.best_r2, 4));
}

El sistema supervisa la calidad del modelo mediante la métrica R², que indica la proporción de varianza explicada:

double CalculateR2()
{
    if (g_pair.data_size < 10) return 0.0;
    
    double sum_actual = 0.0;
    double sum_squared_total = 0.0;
    double sum_squared_residual = 0.0;
    
    // Cálculo de la media
    for (int i = 0; i < g_pair.data_size; i++)
        sum_actual += g_pair.prices[i];
    double mean_actual = sum_actual / g_pair.data_size;
    
    // Cálculo de los componentes de R²
    for (int i = 0; i < g_pair.data_size; i++)
    {
        double actual = g_pair.prices[i];
        double predicted = 0.0;
        
        for (int j = 0; j < 25; j++)
            predicted += g_pair.coeffs[j] * g_pair.features[i][0][j];
        
        double residual = actual - predicted;
        double total_variance = actual - mean_actual;
        
        sum_squared_residual += residual * residual;
        sum_squared_total += total_variance * total_variance;
    }
    
    if (sum_squared_total <= 0.0) return 0.0;
    
    return 1.0 - (sum_squared_residual / sum_squared_total);
}



Entrenamiento en línea de la red neuronal sobre los residuos

El entrenamiento en línea de la componente de red neuronal supone una tarea más compleja en comparación con la optimización lineal. La red neuronal no se reentrena en cada ciclo de optimización, sino de forma periódica —cada 5 ciclos—, lo que evita el sobreajuste y reduce la carga computacional.

void TrainUTransformer()
{
    if (g_pair.data_size < 10)
    {
        Print("Insufficient data for U-Transformer training: ", g_pair.data_size);
        return;
    }
    
    // Cálculo de los residuos a partir del modelo lineal optimizado
    for (int i = 0; i < g_pair.data_size; i++)
    {
        double linear_pred = 0;
        for (int j = 0; j < 25; j++)
            linear_pred += g_pair.coeffs[j] * g_pair.features[i][0][j];
        
        g_pair.neural_net.residuals[i] = g_pair.prices[i] - linear_pred;
    }
    
    Print("Training U-Transformer on ", g_pair.data_size, " residuals...");
    
    double best_loss = 1e6;
    int no_improve_count = 0;

El procedimiento de entrenamiento utiliza una versión simplificada de la retropropagación, adaptada a las limitaciones de MQL5:

for (int epoch = 0; epoch < NeuralEpochs; epoch++)
    {
        double total_loss = 0;
        
        for (int i = 0; i < g_pair.data_size; i++)
        {
            // pasada directa
            double prediction = UTransformerForward(g_pair.coeffs, 
                                                   g_pair.neural_net.residuals[i]);
            g_pair.neural_net.neural_predictions[i] = prediction;
            
            // Cálculo de la pérdida MSE
            double error = prediction - g_pair.neural_net.residuals[i];
            total_loss += error * error;
            
            // Retropropagación simplificada
            double gradient = 2.0 * error / g_pair.data_size;
            
            // Actualización de los pesos (esquema simplificado)
            for (int layer = NeuralLayers - 1; layer >= 0; layer--)
            {
                for (int j = 0; j < NeuralNodes; j++)
                {
                    for (int k = 0; k < NeuralNodes; k++)
                    {
                        g_pair.neural_net.encoder_layers[layer].weights[k][j] -=
                            g_pair.neural_net.learning_rate * gradient * 0.01;
                    }
                }
            }
        }
        
        total_loss /= g_pair.data_size;
        
        // Mecanismo de parada temprana
        if (total_loss < best_loss)
        {
            best_loss = total_loss;
            no_improve_count = 0;
        }
        else
        {
            no_improve_count++;
            if (no_improve_count > 5) break; // Parada ante la ausencia de mejoras
        }
        
        if (epoch % 5 == 0)
            Print("U-Transformer epoch ", epoch, " loss: ", DoubleToString(total_loss, 6));
    }

La finalización del entrenamiento de la red neuronal incluye la actualización de las métricas y del estado de la red:

g_pair.neural_net.loss = best_loss;
    g_pair.neural_net.is_trained = true;
    g_pair.neural_net.training_steps++;
    
    Print("U-Transformer training completed. Loss: ", DoubleToString(best_loss, 6));
}

El criterio de calidad de la red neuronal (loss < 0.01) determina cuándo el sistema puede basarse en las señales de la red neuronal:

// En la función de generación de señales
if (g_pair.neural_net.is_trained && g_pair.neural_net.loss < 0.01)
{
    // Utilizamos U-Transformer para generar señales
    double neural_prediction = UTransformerForward(g_pair.coeffs, 0);
    // ...
}

Esta arquitectura de entrenamiento en línea permite que el componente neuronal se adapte a las condiciones cambiantes del mercado, manteniendo al mismo tiempo la eficiencia computacional y evitando el sobreajuste.

Hemos tomado como base el asesor experto del artículo sobre modelos de regresión y le hemos añadido nuestro modelo híbrido.

Analicemos la prueba del modelo en el símbolo EURUSD, en el marco temporal M15, para el periodo a partir del 1 de julio de 2025:

El coeficiente de Sharpe ha resultado ser bastante bueno:


Conclusión

El sistema híbrido presentado demuestra la viabilidad práctica de integrar arquitecturas modernas de aprendizaje profundo con métodos econométricos clásicos dentro de las limitaciones de la plataforma de trading MQL5. La descomposición en dos etapas del problema de predicción en un componente lineal y otro no lineal permite aprovechar las ventajas de ambos enfoques y minimizar sus inconvenientes.

Entre los principales logros de este trabajo se incluyen la adaptación satisfactoria de la arquitectura U-Net para el análisis de series temporales financieras, la integración de mecanismos de atención para modelar dependencias a largo plazo y la implementación de un sistema automático de conmutación entre fuentes de señales. El sistema muestra un funcionamiento estable en distintos regímenes de mercado, gracias a la ponderación adaptativa de los componentes y a la reoptimización periódica de los parámetros.

El valor práctico de la solución radica en que está lista para su uso en el trading real. El sistema incluye una lógica de trading completa con gestión de posiciones, gestión de riesgos y registro detallado de las operaciones. La separación de símbolos para el análisis y el trading amplía las posibilidades de aprovechar las correlaciones entre mercados.

Sin embargo, hay que reconocer con franqueza las limitaciones de la implementación actual. La retropropagación simplificada, la ausencia de técnicas modernas de regularización y la arquitectura fija de la red limitan el potencial del componente de red neuronal. El uso de arrays estáticos en lugar de estructuras de datos dinámicas impone estrictas limitaciones a la escalabilidad del sistema.

No obstante, el enfoque propuesto abre una vía prometedora para el desarrollo de sistemas de trading. El concepto de entrenamiento de la red neuronal a partir de los residuos de un modelo lineal puede aplicarse a una clase más amplia de problemas de predicción, sin limitarse a los mercados financieros. La arquitectura modular del sistema permite experimentar fácilmente con diferentes tipos de redes neuronales y estrategias de gestión de posiciones.

Resulta especialmente valioso demostrar que los métodos avanzados de aprendizaje automático pueden implementarse incluso en entornos de desarrollo limitados, como MQL5. Esto abre nuevas posibilidades para un amplio abanico de traders y desarrolladores que no tienen acceso a frameworks modernos de aprendizaje profundo.


Bibliografía

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

Archivos adjuntos |
Stanislav Korotky
Stanislav Korotky | 22 ago 2025 en 13:18
En el gráfico del saldo no hay promediado ni «piramidado», tal y como se había afirmado, ya que, para que se dieran estos fenómenos, el histograma de las aportaciones al depósito debería tener una forma de sierra.
Herramientas de trading de MQL5 (Parte 6): Panel de control holográfico dinámico con animaciones de pulso y controles interactivos Herramientas de trading de MQL5 (Parte 6): Panel de control holográfico dinámico con animaciones de pulso y controles interactivos
En este artículo, creamos un panel de control holográfico dinámico en MQL5 para monitorizar símbolos y marcos temporales con RSI, alertas de volatilidad y opciones de ordenación. Añadimos animaciones de pulso, botones interactivos y efectos holográficos para que la herramienta sea visualmente atractiva y responda con agilidad.
Redes neuronales en el trading: Modelo de consultas temporales (Final) Redes neuronales en el trading: Modelo de consultas temporales (Final)
Les presentamos la fase final de la implementación y las pruebas del framework TQNet, en la que la teoría se une a la práctica real de trading. Recorreremos todo el proceso, desde el entrenamiento histórico hasta la prueba de estrés con datos recientes del mercado, evaluando la solidez y la precisión del modelo. Los resultados finales no son solo cifras frías, sino también una demostración clara del valor práctico del enfoque propuesto.
Redes neuronales en el trading: entrenamiento de metaparámetros basado en la heterogeneidad (HimNet) Redes neuronales en el trading: entrenamiento de metaparámetros basado en la heterogeneidad (HimNet)
Te invitamos a conocer el framework HimNet, que combina la flexibilidad de la adaptación espaciotemporal con una alta eficiencia computacional, lo que permite obtener pronósticos precisos y estables en series temporales financieras. En el artículo se muestra en detalle cómo interactúan entre sí sus componentes clave, convirtiendo algoritmos complejos en una arquitectura manejable.
Redes neuronales en el trading: Framework de predicción cruzada de dominios de series temporales (Final) Redes neuronales en el trading: Framework de predicción cruzada de dominios de series temporales (Final)
El artículo trata sobre la construcción práctica del modelo TimeFound para la predicción de series temporales. Asimismo, se analizan las etapas clave de la implementación de los principales enfoques del framework con MQL5.