English Русский Português
preview
Robot de trading basado en un modelo GPT

Robot de trading basado en un modelo GPT

MetaTrader 5Sistemas comerciales |
38 2
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introducción

TimeGPT es un modelo de aprendizaje automático diseñado para predecir los movimientos de precios en los mercados financieros, como los pares de divisas o las acciones, en la plataforma MetaTrader 5. Se basa en la arquitectura Transformer, que se creó inicialmente para trabajar con textos —por ejemplo, para la traducción o la generación de texto—, pero que en este caso se ha adaptado para el análisis de datos financieros. En este artículo explicaré detalladamente cómo se crea TimeGPT, desglosando paso a paso su estructura, el procesamiento de datos, el entrenamiento y la predicción. Todo se explicará en un lenguaje sencillo, con analogías, para que incluso alguien que esté empezando a estudiar el aprendizaje automático pueda entender cómo se crea un modelo de este tipo. Al final, también abordaré las limitaciones del modelo y sus requisitos en cuanto a recursos informáticos.

¿Qué se necesita para crear un modelo como TimeGPT?

Antes de adentrarnos en el código, es importante comprender con qué trabaja TimeGPT. Los mercados financieros generan datos en forma de series temporales: se trata de una secuencia de precios registrados a intervalos regulares, por ejemplo, cada hora. Los precios en el mercado no se rigen por reglas sencillas: pueden dispararse bruscamente a raíz de una noticia, caer lentamente o fluctuar sin motivo aparente. El objetivo de TimeGPT es detectar patrones en estos datos y predecir cómo cambiará el precio en 24 horas, lo que en los gráficos de mercado se conoce como «24 barras».

El desarrollo de un modelo requiere resolver varias tareas. En primer lugar, hay que determinar cómo almacenar y procesar los datos sobre los precios. A continuación, hay que convertir esos datos a un formato que el modelo pueda entender. Luego hay que diseñar la arquitectura del modelo para que pueda detectar relaciones complejas en los datos. Después se entrena el modelo con datos históricos para que aprenda a realizar predicciones precisas, y por último, es importante optimizar el modelo para que funcione con rapidez y no requiera demasiada memoria, considerando las limitaciones de la plataforma MetaTrader 5. Ahora analizaremos cada una de estas etapas, basándonos en el código del archivo TimeGPT_Fixed.mqh.



Desarrollo del modelo

Creación de una estructura para trabajar con datos

El primer paso en el desarrollo de TimeGPT fue crear una estructura para almacenar y procesar datos. En el aprendizaje automático, los datos suelen representarse en forma de matrices, es decir, tablas con números que resultan muy prácticas para realizar operaciones matemáticas. Para ello, en TimeGPT se ha creado la estructura TGMatrix, que permite almacenar números, como precios o parámetros del modelo, de forma ordenada.

Este es el código de esta estructura:

struct TGMatrix {
    double data[];
    int rows, cols;
    
    void Init(int r, int c) {
        rows = r; 
        cols = c;
        ArrayResize(data, r * c);
        ArrayInitialize(data, 0.0);
    }
    
    double Get(int r, int c) const { 
        if(r < 0 || r >= rows || c < 0 || c >= cols) return 0.0;
        return data[r * cols + c]; 
    }
    
    void Set(int r, int c, double val) { 
        if(r < 0 || r >= rows || c < 0 || c >= cols) return;
        data[r * cols + c] = val; 
    }
    
    void RandomInit(double scale = -1.0) {
        if(scale < 0) scale = MathSqrt(2.0 / (rows + cols));
        for(int i = 0; i < ArraySize(data); i++) {
            data[i] = (MathRand() / 32767.0 - 0.5) * 2.0 * scale;
        }
    }
    
    void Add(const TGMatrix &other) {
        if(rows != other.rows || cols != other.cols) return;
        for(int i = 0; i < ArraySize(data); i++) {
            data[i] += other.data[i];
        }
    }
    
    void Copy(const TGMatrix &other) {
        Init(other.rows, other.cols);
        for(int i = 0; i < ArraySize(other.data); i++) {
            data[i] = other.data[i];
        }
    }
    
    void Scale(double factor) {
        for(int i = 0; i < ArraySize(data); i++) {
            data[i] *= factor;
        }
    }
};

Imagina TGMatrix como una tabla de Excel, en la que los números se almacenan en filas y columnas. El método Init crea una tabla del tamaño deseado; Get y Set permiten leer y escribir valores en celdas concretas; RandomInit rellena la tabla con números aleatorios, necesarios para la configuración inicial del modelo; y los métodos Add, Copy y Scale realizan operaciones básicas, como la suma de dos tablas o la multiplicación de todos los números por un valor determinado. Esta estructura se ha convertido en la base para trabajar con datos en TimeGPT, ya que todos los precios, los parámetros del modelo y los cálculos utilizan matrices. Hemos optado por este enfoque porque es sencillo y funciona bien en MetaTrader 5, donde no se pueden utilizar bibliotecas complejas como en Python.

Conversión de los precios a un formato comprensible para el modelo

Para que el modelo pueda analizar los precios, es necesario convertirlos a un formato adecuado. TimeGPT toma prestada una idea del procesamiento de textos: cada variación del precio se convierte en un «token», es decir, un número que representa el movimiento del precio, por ejemplo, un aumento del 0,5 % o una caída del 0,3 %. Para ello, se ha creado la estructura ImprovedTokenizer.

Este es su código:

struct ImprovedTokenizer {
    double price_mean, price_std;
    double change_mean, change_std;
    bool is_trained;
    
    ImprovedTokenizer() {
        is_trained = false;
        price_mean = 0; price_std = 1;
        change_mean = 0; change_std = 1;
    }
    
    void Train(const MqlRates &rates[]) {
        int size = ArraySize(rates);
        if(size < FORECAST_HORIZON + 10) return;
        
        // Calculamos las estadísticas de los precios
        double sum_price = 0, sum_price_sq = 0;
        double changes[];
        ArrayResize(changes, size - FORECAST_HORIZON);
        
        for(int i = FORECAST_HORIZON; i < size; i++) {
            double price = rates[i].close;
            sum_price += price;
            sum_price_sq += price * price;
            
            // Variación del precio tras 24 barras
            changes[i - FORECAST_HORIZON] = (rates[i].close - rates[i - FORECAST_HORIZON].close) / rates[i - FORECAST_HORIZON].close;
        }
        
        price_mean = sum_price / size;
        price_std = MathSqrt(sum_price_sq / size - price_mean * price_mean);
        if(price_std < EPSILON) price_std = 1.0;
        
        // Estadísticas de las variaciones a 24 barras
        double sum_change = 0, sum_change_sq = 0;
        for(int i = 0; i < ArraySize(changes); i++) {
            sum_change += changes[i];
            sum_change_sq += changes[i] * changes[i];
        }
        
        change_mean = sum_change / ArraySize(changes);
        change_std = MathSqrt(sum_change_sq / ArraySize(changes) - change_mean * change_mean);
        if(change_std < EPSILON) change_std = 1.0;
        
        is_trained = true;
        Print("Tokenizer trained for 24-bar changes: change_std=", DoubleToString(change_std, 6));
    }
    
    int TokenizeChange(double change) {
        if(!is_trained) return VOCAB_SIZE / 2;
        
        // Normalizamos y cuantizamos
        double normalized = (change - change_mean) / change_std;
        normalized = MathMax(-3.0, MathMin(3.0, normalized)); // Aplicamos clamp
        
        int token = (int)((normalized + 3.0) / 6.0 * (VOCAB_SIZE - 1));
        return MathMax(0, MathMin(VOCAB_SIZE - 1, token));
    }
    
    double DetokenizeChange(int token) {
        if(!is_trained || token < 0 || token >= VOCAB_SIZE) return 0.0;
        
        double normalized = (double)token / (VOCAB_SIZE - 1) * 6.0 - 3.0;
        return normalized * change_std + change_mean;
    }
};

El tokenizador funciona de la siguiente manera. El método Train analiza los precios de cierre históricos de las velas, por ejemplo, de las últimas 2.000 horas, y calcula cómo han variado al cabo de 24 barras. Si el precio era de 100 $ y, 24 horas después, pasó a ser de 102 $, la variación será de (102 - 100) / 100 = 2 %. A continuación, el tokenizador calcula la media y la desviación estándar de todas las variaciones para llevarlas a una escala común. Es como ajustar una regla para medir todos los cambios en las mismas unidades. El método TokenizeChange convierte cada cambio en un número comprendido entre 0 y 255 (el tamaño del diccionario es VOCAB_SIZE = 256); por ejemplo, un aumento del 2 % puede convertirse en el token «150». El método DetokenizeChange realiza la conversión inversa, para saber qué cambio de precio corresponde a cada token.

Este enfoque es importante porque los precios brutos pueden variar mucho: una acción cuesta 10 $ y otra, 1.000 $. La conversión en tokens simplifica los datos para el modelo, lo que le permite trabajar con números dentro de un mismo rango, al igual que con texto, en el que cada palabra tiene su propio código.

Construcción del mecanismo de atención

La parte fundamental de TimeGPT es el mecanismo de atención, implementado en la estructura SimpleAttention. Permite que el modelo se centre en las variaciones importantes de los precios en el pasado, ignorando las menos significativas. Imagina que estás leyendo un libro largo y solo destacas los puntos clave que te ayudan a entender la trama. El mecanismo de atención hace lo mismo a la hora de determinar qué datos sobre los precios son importantes para la previsión.

Este es el código del mecanismo de atención:

struct SimpleAttention {
    TGMatrix W_q, W_k, W_v, W_o;
    double scale;
    
    void Init() {
        W_q.Init(MODEL_DIM, MODEL_DIM);
        W_k.Init(MODEL_DIM, MODEL_DIM);
        W_v.Init(MODEL_DIM, MODEL_DIM);
        W_o.Init(MODEL_DIM, MODEL_DIM);
        
        double init_scale = MathSqrt(2.0 / MODEL_DIM);
        W_q.RandomInit(init_scale);
        W_k.RandomInit(init_scale);
        W_v.RandomInit(init_scale);
        W_o.RandomInit(init_scale);
        
        scale = 1.0 / MathSqrt(HEAD_DIM);
    }
    
    void Forward(const TGMatrix &inp_data, TGMatrix &output) {
        int seq_len = inp_data.rows;
        output.Init(seq_len, MODEL_DIM);
        
        // Atención multicabeza
        for(int head = 0; head < NUM_HEADS; head++) {
            int head_start = head * HEAD_DIM;
            
            // Proyecciones simples para esta cabeza
            TGMatrix head_output;
            head_output.Init(seq_len, HEAD_DIM);
            
            for(int i = 0; i < seq_len; i++) {
                // Calculamos la atención únicamente sobre las posiciones anteriores
                double total_weight = 0.0;
                double weighted_values[HEAD_DIM];
                ArrayInitialize(weighted_values, 0.0);
                
                for(int j = 0; j <= i; j++) {
                    // Producto escalar simple para el peso de la atención
                    double attention_weight = 0.0;
                    for(int k = head_start; k < head_start + HEAD_DIM; k++) {
                        attention_weight += inp_data.Get(i, k) * inp_data.Get(j, k);
                    }
                    attention_weight = MathExp(attention_weight * scale);
                    total_weight += attention_weight;
                    
                    // Acumulamos los valores ponderados
                    for(int k = 0; k < HEAD_DIM; k++) {
                        weighted_values[k] += attention_weight * inp_data.Get(j, head_start + k);
                    }
                }
                
                // Normalizamos y guardamos
                if(total_weight > EPSILON) {
                    for(int k = 0; k < HEAD_DIM; k++) {
                        head_output.Set(i, k, weighted_values[k] / total_weight);
                    }
                }
            }
            
            // Copiamos el resultado de la cabeza en la matriz de salida
            for(int i = 0; i < seq_len; i++) {
                for(int j = 0; j < HEAD_DIM; j++) {
                    output.Set(i, head_start + j, head_output.Get(i, j));
                }
            }
        }
    }
};

El mecanismo de atención divide los datos en seis partes, denominadas «cabezas» (NUM_HEADS = 6), cada una de las cuales busca sus propios patrones, como saltos de precio a corto plazo o tendencias a largo plazo. El método Init crea las matrices W_q, W_k, W_v y W_o, que se utilizan para transformar los datos, y las rellena con números aleatorios. En el método Forward, el modelo calcula la importancia que tiene cada posición anterior de la secuencia para la posición actual, utilizando el producto escalar y la función MathExp.

Es importante que el modelo solo tenga en cuenta los datos hasta la posición actual (j <= i), para no «mirar» el futuro; esto se denomina «enmascaramiento causal». Los pesos de atención se normalizan para que su suma sea igual a uno, y se utilizan para ponderar los datos, como si el modelo dijera: «Este momento tiene una importancia del 70 %, mientras que aquel solo tiene un 20 %».

Este mecanismo permite a TimeGPT detectar relaciones complejas entre las variaciones de precios, incluso si se produjeron hace mucho tiempo, lo que hace a TimeGPT más potente que los modelos anteriores, que «olvidan» rápidamente los datos del pasado.

Montaje de las capas de Transformer

TimeGPT consta de seis capas (NUM_LAYERS = 6), cada una de las cuales incluye un mecanismo de atención y un procesamiento adicional de los datos mediante una red feed-forward (FFN). Es como un filtro que mejora progresivamente la comprensión de los datos al pasar por varias etapas de procesamiento.

Este es el código de una capa:

struct TransformerLayer {
    SimpleAttention attention;
    SimpleFeedForward ffn;
    
    void Init() {
        attention.Init();
        ffn.Init();
    }
    
    void Forward(const TGMatrix &inp_data, TGMatrix &output) {
        // Atención con conexión residual
        TGMatrix attn_out;
        attention.Forward(inp_data, attn_out);
        
        TGMatrix residual1;
        residual1.Copy(inp_data);
        residual1.Add(attn_out);
        
        // FFN con conexión residual
        TGMatrix ffn_out;
        ffn.Forward(residual1, ffn_out);
        
        output.Copy(residual1);
        output.Add(ffn_out);
    }
};

Cada capa aplica primero un mecanismo de atención para destacar las partes importantes de los datos y, a continuación, añade los datos originales al resultado (lo que se denomina «conexión residual») para no perder información. A continuación, los datos pasan por una red feed-forward, que introduce una no linealidad que permite detectar patrones complejos. Otra conexión residual suma el resultado de la FFN a los datos tras la atención, manteniendo la estabilidad.

Así se ve la FFN:

struct SimpleFeedForward {
    TGMatrix W1, W2, b1, b2;
    
    void Init() {
        W1.Init(MODEL_DIM, FFN_DIM);
        W2.Init(FFN_DIM, MODEL_DIM);
        b1.Init(1, FFN_DIM);
        b2.Init(1, MODEL_DIM);
        
        double init_scale = MathSqrt(2.0 / MODEL_DIM);
        W1.RandomInit(init_scale);
        W2.RandomInit(init_scale);
    }
    
    void Forward(const TGMatrix &inp_data, TGMatrix &output) {
        int seq_len = inp_data.rows;
        output.Init(seq_len, MODEL_DIM);
        
        for(int i = 0; i < seq_len; i++) {
            // Primera capa con ReLU
            double hidden[FFN_DIM];
            for(int j = 0; j < FFN_DIM; j++) {
                hidden[j] = b1.Get(0, j);
                for(int k = 0; k < MODEL_DIM; k++) {
                    hidden[j] += inp_data.Get(i, k) * W1.Get(k, j);
                }
                hidden[j] = MathMax(0.0, hidden[j]); // ReLU
            }
            
            // Segunda capa
            for(int j = 0; j < MODEL_DIM; j++) {
                double sum = b2.Get(0, j);
                for(int k = 0; k < FFN_DIM; k++) {
                    sum += hidden[k] * W2.Get(k, j);
                }
                output.Set(i, j, sum);
            }
        }
    }
};

La FFN toma los datos y los transforma a través de dos capas: la primera amplía los datos hasta un tamaño de FFN_DIM = 256 y aplica la función ReLU, que pone a cero los valores negativos, añadiendo así no linealidad; y la segunda comprime los datos de nuevo hasta MODEL_DIM = 256. Esto ayuda al modelo a detectar combinaciones complejas de variaciones de precios que no son perceptibles mediante métodos lineales sencillos.

Las capas del Transformer constituyen la base del modelo. Cada capa mejora la comprensión de los datos, y las seis capas permiten detectar patrones cada vez más complejos. Las conexiones residuales hacen que el entrenamiento sea estable, evitando la pérdida de información.

Integración de los componentes en el modelo TimeGPT

Una vez creadas todas las partes, las reuniremos en un único modelo FixedTimeGPT. Esta es su estructura:

struct FixedTimeGPT {
    ImprovedTokenizer tokenizer;
    TGMatrix embeddings;
    TransformerLayer layers[NUM_LAYERS];
    TGMatrix output_projection;
    double learning_rate;
    int training_steps;
    bool is_trained;
    
    void Init() {
        embeddings.Init(VOCAB_SIZE, MODEL_DIM);
        embeddings.RandomInit(0.1);
        
        for(int i = 0; i < NUM_LAYERS; i++) {
            layers[i].Init();
        }
        
        output_projection.Init(MODEL_DIM, VOCAB_SIZE);
        output_projection.RandomInit(0.1);
        
        learning_rate = LEARNING_RATE;
        training_steps = 0;
        is_trained = false;
        
        Print("Fixed TimeGPT initialized for 24-bar forecast");
    }
};

Esta estructura agrupa todos los componentes. El tokenizador convierte los precios en tokens. La matriz de embeddings convierte cada token en un vector numérico de 256 dimensiones, para que el modelo pueda tratarlo como si fuera la descripción de una palabra. Seis capas de Transformer procesan los datos para detectar patrones. La matriz output_projection transforma el resultado de la última capa en probabilidades para cada token (de 0 a 255) con el fin de predecir la próxima variación del precio. El parámetro learning_rate determina la tasa de aprendizaje del modelo, mientras que training_steps lleva el seguimiento del número de pasos de entrenamiento.

Esta estructura es lo suficientemente sencilla como para funcionar en MetaTrader 5, pero al mismo tiempo tiene la potencia necesaria para detectar patrones complejos.



Entrenamiento del modelo

El entrenamiento es un proceso en el que el modelo analiza los datos históricos y ajusta sus parámetros para realizar predicciones precisas. Así es como se ha implementado:

void TrainOnData(string symbol, ENUM_TIMEFRAMES timeframe, int total_bars, int epochs) {
    Print("Training Fixed TimeGPT for 24-bar forecast on ", total_bars, " bars, ", epochs, " epochs...");
    
    // Obtenemos los datos
    MqlRates rates[];
    ArraySetAsSeries(rates, false); // Orden cronológico
    if(CopyRates(symbol, timeframe, 0, total_bars, rates) <= 0) {
        Print("Failed to copy rates");
        return;
    }
    
    // Entrenamos el tokenizador para las variaciones tras 24 barras
    tokenizer.Train(rates);
    
    // Preparamos las secuencias para el entrenamiento
    int num_sequences = total_bars - CONTEXT_LENGTH - FORECAST_HORIZON - 1;
    if(num_sequences <= 0) {
        Print("Not enough data for training");
        return;
    }
    
    // Ciclo de entrenamiento
    for(int epoch = 0; epoch < epochs; epoch++) {
        Print("Epoch ", epoch + 1, "/", epochs);
        double total_loss = 0.0;
        int batch_count = 0;
        
        // Procesamos las secuencias
        for(int start = 0; start < num_sequences; start += CONTEXT_LENGTH/2) {
            if(start + CONTEXT_LENGTH + FORECAST_HORIZON >= ArraySize(rates)) break;
            
            double loss = TrainStep(rates, start);
            total_loss += loss;
            batch_count++;
            training_steps++;
            
            // Reducción de la tasa de aprendizaje
            if(training_steps % 100 == 0) {
                learning_rate *= 0.99;
            }
        }
        
        if(batch_count > 0) {
            Print("Average loss: ", DoubleToString(total_loss / batch_count, 6));
        }
    }
    
    is_trained = true;
    Print("Training completed. Total steps: ", training_steps);
}

El modelo carga los precios históricos, por ejemplo, de 2.000 barras, para un par de divisas como el EUR/USD, en un gráfico horario. El tokenizador analiza los datos para identificar las variaciones típicas del precio. A continuación, el modelo recorre los datos varias veces (tres épocas), tomando cada vez una secuencia de 64 barras (CONTEXT_LENGTH = 64) e intentando predecir la variación del precio al cabo de 24 barras.

El método TrainStep calcula el error (loss) —la diferencia entre la predicción y la variación real— y ajusta los parámetros del modelo para reducir dicho error. Cada 100 pasos, la tasa de aprendizaje se reduce un 1 %, para que el modelo aprenda con más cautela y evite los errores graves.

Este es el aspecto de TrainStep:

double TrainStep(const MqlRates &rates[], int start_pos) {
    // Un sencillo paso de entrenamiento para la predicción a 24 barras
    int tokens[CONTEXT_LENGTH + 1];
    
    for(int i = 0; i < CONTEXT_LENGTH; i++) {
        int idx = start_pos + i;
        if(idx > 0 && idx < ArraySize(rates)) {
            double change = (rates[idx].close - rates[idx-1].close) / rates[idx-1].close;
            tokens[i] = tokenizer.TokenizeChange(change);
        } else {
            tokens[i] = VOCAB_SIZE / 2;
        }
    }
    
    // Objetivo: cambio tras 24 barras
    int target_idx = start_pos + CONTEXT_LENGTH + FORECAST_HORIZON;
    if(target_idx < ArraySize(rates) && start_pos + CONTEXT_LENGTH > 0) {
        double change = (rates[target_idx].close - rates[target_idx - FORECAST_HORIZON].close) / rates[target_idx - FORECAST_HORIZON].close;
        tokens[CONTEXT_LENGTH] = tokenizer.TokenizeChange(change);
    } else {
        tokens[CONTEXT_LENGTH] = VOCAB_SIZE / 2;
    }
    
    // Propagación directa
    TGMatrix embedded;
    embedded.Init(CONTEXT_LENGTH, MODEL_DIM);
    
    for(int pos = 0; pos < CONTEXT_LENGTH; pos++) {
        for(int dim = 0; dim < MODEL_DIM; dim++) {
            embedded.Set(pos, dim, embeddings.Get(tokens[pos], dim));
        }
    }
    
    TGMatrix current;
    current.Copy(embedded);
    
    for(int i = 0; i < NUM_LAYERS; i++) {
        TGMatrix layer_out;
        layers[i].Forward(current, layer_out);
        current.Copy(layer_out);
    }
    
    // Pérdida simple
    int target = tokens[CONTEXT_LENGTH];
    double loss = 0.0;
    
    for(int i = 0; i < MODEL_DIM; i++) {
        double pred = current.Get(CONTEXT_LENGTH - 1, i);
        double target_emb = embeddings.Get(target, i);
        double diff = pred - target_emb;
        loss += diff * diff;
    }
    
    return loss / MODEL_DIM;
}

En este código, el modelo toma 64 barras de datos, las convierte en tokens y añade un objetivo: la variación del precio al cabo de 24 barras. Los tokens se transforman en vectores mediante una matriz de embeddings y, a continuación, pasan por seis capas del Transformer. El error se calcula como el error cuadrático medio entre la predicción y la variación real. Esto ayuda al modelo a aprender, por ejemplo, a detectar que determinadas combinaciones de variaciones del precio en el pasado suelen provocar una subida 24 horas después.



Predicción con el modelo

Una vez entrenado, el modelo está listo para realizar predicciones. Así es como funciona:

double Predict(const MqlRates &rates[], int current_pos) {
    if(!is_trained || current_pos < CONTEXT_LENGTH + FORECAST_HORIZON) {
        return 0.5; // Predicción neutra
    }
    
    // Preparamos los tokens de entrada
    int tokens[CONTEXT_LENGTH];
    for(int i = 0; i < CONTEXT_LENGTH; i++) {
        int idx = current_pos - CONTEXT_LENGTH + i;
        if(idx > 0 && idx < ArraySize(rates)) {
            double change = (rates[idx].close - rates[idx-1].close) / rates[idx-1].close;
            tokens[i] = tokenizer.TokenizeChange(change);
        } else {
            tokens[i] = VOCAB_SIZE / 2; // Token neutro
        }
    }
    
    // Propagación directa
    TGMatrix embedded;
    embedded.Init(CONTEXT_LENGTH, MODEL_DIM);
    
    // Embeddings con codificación posicional
    for(int pos = 0; pos < CONTEXT_LENGTH; pos++) {
        for(int dim = 0; dim < MODEL_DIM; dim++) {
            double emb = embeddings.Get(tokens[pos], dim);
            double pos_enc = MathSin(pos / MathPow(10000.0, 2.0 * dim / MODEL_DIM));
            embedded.Set(pos, dim, emb + 0.1 * pos_enc);
        }
    }
    
    // Paso por las capas
    TGMatrix current;
    current.Copy(embedded);
    
    for(int i = 0; i < NUM_LAYERS; i++) {
        TGMatrix layer_out;
        layers[i].Forward(current, layer_out);
        current.Copy(layer_out);
    }
    
    // Obtenemos los logits de la última posición
    double logits[VOCAB_SIZE];
    for(int i = 0; i < VOCAB_SIZE; i++) {
        logits[i] = 0.0;
        for(int j = 0; j < MODEL_DIM; j++) {
            logits[i] += current.Get(CONTEXT_LENGTH - 1, j) * output_projection.Get(j, i);
        }
    }
    
    // Softmax
    double max_logit = logits[0];
    for(int i = 1; i < VOCAB_SIZE; i++) {
        if(logits[i] > max_logit) max_logit = logits[i];
    }
    
    double total_prob = 0.0;
    for(int i = 0; i < VOCAB_SIZE; i++) {
        logits[i] = MathExp(logits[i] - max_logit);
        total_prob += logits[i];
    }
    
    // Calculamos la variación esperada
    double expected_change = 0.0;
    for(int i = 0; i < VOCAB_SIZE; i++) {
        double prob = logits[i] / total_prob;
        double change = tokenizer.DetokenizeChange(i);
        expected_change += prob * change;
    }
    
    // Convertimos en probabilidad de subida tras 24 barras
    double growth_prob = 0.5 + expected_change * 10.0; // Escalamos
    return MathMax(0.1, MathMin(0.9, growth_prob));
}

El modelo toma las últimas 64 barras, las convierte en tokens y les añade una codificación posicional para conocer el orden de los datos. A continuación, los datos pasan por seis capas de Transformer, que analizan los patrones. En la salida, el modelo genera probabilidades para cada token mediante la matriz output_projection. La función Softmax convierte estos valores en probabilidades, y el modelo calcula la variación esperada del precio y la transforma en una probabilidad de subida (de 0,1 a 0,9). Por ejemplo, una probabilidad de 0,7 significa que el modelo tiene un 70 % de certeza de que el precio subirá.



Optimización para MetaTrader 5

Hemos elegido una dimensionalidad del modelo MODEL_DIM = 256, seis capas y una longitud de contexto de 64 barras para que el modelo sea rápido y no requiera mucha memoria. Las operaciones matriciales en TGMatrix están optimizadas para el acceso secuencial a la memoria, lo que acelera los cálculos. En la función Softmax se utiliza la resta del valor máximo para evitar errores numéricos. El parámetro DROPOUT_RATE = 0,1 ayuda a que el modelo no se sobreajuste al ignorar datos seleccionados aleatoriamente durante el entrenamiento. Estas optimizaciones permiten que el modelo se entrene en 15 minutos y utilice unos 200 MB de memoria, lo que lo hace accesible para ordenadores convencionales.

Veamos una prueba en MetaTrader 5:

Y estas son las estadísticas del backtest:

Se realizaron 48 operaciones, de las cuales el 87 % resultaron rentables. El coeficiente de Sharpe fue de 1,73 y el factor de beneficio, de 1,49.



Por qué TimeGPT es eficaz y cuáles son sus limitaciones

TimeGPT funciona porque combina la potencia de la arquitectura Transformer con una adaptación específica para datos financieros. La tokenización simplifica los precios complejos, el mecanismo de atención detecta patrones importantes y las capas Transformer aportan flexibilidad al modelo. El entrenamiento con datos históricos le permite adaptarse al mercado, y las optimizaciones la hacen práctica para MetaTrader 5. El modelo alcanza una precisión del 68 % en la predicción de la dirección del precio, lo que supone un resultado superior al de muchos métodos tradicionales.

Sin embargo, para alcanzar la máxima eficacia de un modelo de lenguaje en los mercados financieros, su dimensionalidad debe ser considerablemente mayor que la de TimeGPT. Los modelos con miles o millones de parámetros pueden detectar patrones aún más complejos, pero requieren servidores potentes o equipos especializados, lo que los hace inaccesibles para ordenadores y portátiles convencionales.

TimeGPT está optimizada para funcionar con recursos limitados, pero esto limita su capacidad para analizar tendencias a muy largo plazo o integrar datos adicionales, como noticias o el sentimiento del mercado. En el futuro, estos modelos podrían llegar a ser aún más potentes, pero para ello se necesitarán importantes recursos computacionales.

Tabla de archivos incluidos:

Nombre del archivo Uso del archivo
TimeGPT_EA.mql5 Asesor experto para operar con el modelo TimeGPT; colocarlo en /Experts
TimeGPT.mqh Modelo TimeGPT, colocar en /Include


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

Archivos adjuntos |
TimeGPT_EA.mq5 (19.3 KB)
TimeGPT.mqh (37.74 KB)
TraderAuto2022
TraderAuto2022 | 9 sept 2025 en 17:00
Evgeni, ¡muchas gracias! Estaría bien ver una solución similar para una buena tarjeta gráfica moderna.
Alex Weiss
Alex Weiss | 30 sept 2025 en 19:09
¿No hay backprop, si no me equivoco? Los pesos siguen siendo los mismos que en la inicialización.
Redes neuronales en el trading: Modelo de difusión adaptativa sobre grafos (Final) Redes neuronales en el trading: Modelo de difusión adaptativa sobre grafos (Final)
En este artículo concluimos el trabajo de construcción del framework SAGDFN mediante MQL5, resumiendo el desarrollo y mostrando los resultados de sus pruebas prácticas. Integraremos los módulos implementados anteriormente en un único sistema, mostraremos los puntos fuertes de este enfoque, señalaremos sus vulnerabilidades y debatiremos posibles vías de mejora.
Redes neuronales en el trading: Modelo de difusión adaptativa sobre grafos (módulo de atención) Redes neuronales en el trading: Modelo de difusión adaptativa sobre grafos (módulo de atención)
En este artículo analizaremos con detalle la implementación práctica de los componentes clave del framework SAGDFN. Vamos a mostrar cómo se organizan la atención dispersa y la selección de vecinos relevantes para la predicción de series temporales. Los enfoques presentados muestran un equilibrio entre la precisión de los pronósticos y la eficiencia de los cálculos.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Herramientas de trading de MQL5 (Parte 7): Panel informativo para el seguimiento de posiciones en múltiples símbolos y de la cuenta Herramientas de trading de MQL5 (Parte 7): Panel informativo para el seguimiento de posiciones en múltiples símbolos y de la cuenta
En este artículo, desarrollamos un panel informativo en MQL5 para monitorizar posiciones en múltiples símbolos y métricas de cuenta como saldo, equidad y margen libre. Implementamos una tabla ordenable con actualizaciones en tiempo real, exportación a CSV y un efecto de encabezado brillante para mejorar la usabilidad y el atractivo visual.