English Русский 中文 Español Português
preview
Training eines nichtlinearen U-Transformers anhand der Residuen eines linearen autoregressiven Modells

Training eines nichtlinearen U-Transformers anhand der Residuen eines linearen autoregressiven Modells

MetaTrader 5 — Experten |
22 1
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Im modernen algorithmischen Handel stehen Entwickler vor einem grundlegenden Dilemma: Lineare Modelle sind einfach und nachvollziehbar, können jedoch komplexe nichtlineare Zusammenhänge in Finanzzeitreihen nicht erfassen. Tiefe neuronale Netze hingegen können theoretisch jede beliebige Nichtlinearität modellieren, neigen jedoch bei starkem Marktrauschen und begrenzten Datenmengen zu Overfitting und Instabilität.

Der Artikel stellt einen innovativen hybriden Ansatz vor, der diesem Dilemma mit einem zweistufigen Modellierungsansatz begegnet: Zunächst extrahiert ein lineares autoregressives Modell mit 25 Merkmalen wichtige statistische Muster aus den Kursdaten; anschließend wird eine spezielle U-Transformer-Architektur auf den Residuen des linearen Modells trainiert, wodurch verborgene nichtlineare Muster aufgedeckt werden, die die erste Stufe nicht erfassen konnte.

Die zentrale Neuerung besteht in der Anpassung der U-Net-Architektur (die ursprünglich für die Bildsegmentierung entwickelt wurde) durch die Integration von Transformer-Blöcken für die Zeitreihenanalyse. Das System ist in MQL5 implementiert und umfasst eine vollständige Handelslogik mit dynamischem Positionsmanagement, automatischer Parameter-Reoptimierung und Online-Training des neuronalen Netzwerks.

Die experimentelle Validierung belegt die Überlegenheit des hybriden Ansatzes gegenüber rein linearen Methoden, wobei die Recheneffizienz und die Interpretierbarkeit der Ergebnisse gewahrt bleiben. Das vorgestellte System ist in der Lage, in Echtzeit zu arbeiten und sich automatisch an veränderte Marktbedingungen anzupassen.



Einleitung

Finanzmärkte sind komplexe adaptive Systeme, in denen traditionelle Prognosemethoden mit mehreren grundlegenden Herausforderungen konfrontiert sind. Das erste und offensichtlichste dieser Probleme ist die Nichtlinearität. Marktdaten offenbaren komplexe Muster, die sich einer einfachen linearen Beschreibung entziehen: Regimewechsel bei der Volatilität, asymmetrische Reaktionen auf Nachrichten und kaskadierende Liquiditätseffekte. Versuche, leistungsstarke nichtlineare Modelle des maschinellen Lernens auf Finanzdaten anzuwenden, führen jedoch häufig zu einem zweiten Problem: Overfitting und Prognoseinstabilität.

Dieses Problem ist im algorithmischen Handel besonders ausgeprägt, da das Modell Kursbewegungen nicht nur präzise prognostizieren, sondern dies auch über verschiedene Marktbedingungen hinweg konsistent leisten und zugleich für den Echtzeitbetrieb recheneffizient bleiben muss. Klassische ökonometrische Modelle wie ARIMA oder VAR bieten Stabilität und Interpretierbarkeit, doch aufgrund ihres linearen Charakters sind sie bei der Modellierung komplexer Marktdynamiken stark eingeschränkt.

Andererseits sind moderne Deep-Learning-Architekturen – LSTM, GRU, Transformer – theoretisch in der Lage, beliebige nichtlineare Zusammenhänge zu approximieren, doch ihre Anwendung auf Finanzdaten wird durch mehrere Faktoren erschwert. Erstens führen stark verrauschte Marktdaten dazu, dass neuronale Netze zufällige Schwankungen auswendig lernen, anstatt die zugrunde liegenden Muster zu erkennen. Zweitens bedeutet die Nichtstationarität finanzieller Zeitreihen, dass aus historischen Daten erlernte Muster schnell irrelevant werden können.

Das dritte Problem ist praktischer Natur: Die rechnerischen Einschränkungen von Handelsplattformen, insbesondere in der Programmiersprache MQL5, lassen eine vollständige Nutzung moderner Deep-Learning-Frameworks nicht zu. Der Entwickler ist gezwungen, Algorithmen für neuronale Netze von Grund auf selbst zu implementieren, was die Komplexität der Architekturen, die realistisch umgesetzt werden können, erheblich einschränkt.

Die in diesem Beitrag vorgeschlagene Lösung basiert auf dem Ansatz, das Prognoseproblem in zwei Phasen unterschiedlicher Art zu unterteilen: eine lineare Phase zur Erfassung der wichtigsten statistischen Regelmäßigkeiten und eine nichtlineare Phase zur Modellierung komplexer Abhängigkeiten in den Residuen. Dieser Ansatz ermöglicht es, die Stärken beider Methodengruppen zu nutzen und gleichzeitig ihre Schwächen zu minimieren.

Das lineare autoregressive Modell der ersten Stufe basiert auf einem sorgfältig konzipierten 25-dimensionalen Merkmalsraum, der nicht nur klassische, verzögerte Kurswerte, sondern auch deren nichtlineare Transformationen, technische Indikatoren und zyklische Komponenten umfasst. Dieses Modell wird mittels Gradientenabstieg optimiert und bietet bei hoher Interpretierbarkeit ein grundlegendes Niveau der Prognosequalität.

Die neuronale Netzkomponente der zweiten Stufe nutzt eine angepasste U-Transformer-Architektur, die die Prinzipien von U-Net (eine Encoder-Decoder-Struktur mit Skip-Verbindungen) und die Attention-Mechanismen der Transformer-Architektur kombiniert. Das wesentliche Merkmal dieses Ansatzes besteht darin, dass das neuronale Netz nicht anhand der ursprünglichen Kursdaten, sondern anhand der Residuen des linearen Modells trainiert wird, was die Aufgabe erheblich vereinfacht und das Risiko von Overfitting verringert.

Diese Zerlegung sorgt für eine natürliche Regularisierung: Wenn sich die Marktbedingungen ändern und die neuronale Netzkomponente instabile Prognosen erzeugt, wechselt das System automatisch zu den konservativeren Prognosen des linearen Modells. Dies wird durch ein adaptives Gewichtungsschema erreicht, bei dem das Gewicht der neuronalen Netzkomponente von der aktuellen Qualität beider Modelle abhängt.



Theoretische Grundlagen der U-Transformer-Architektur

Die U-Transformer-Architektur ist eine Synthese aus zwei leistungsstarken Konzepten aus den Bereichen Computer Vision und Verarbeitung natürlicher Sprache: U-Net und Attention-Mechanismen. U-Net wurde ursprünglich für Aufgaben der medizinischen Bildsegmentierung entwickelt, bei denen eine genaue Objektlokalisierung unter Beibehaltung des globalen Kontexts erforderlich ist. Der Kerngedanke von U-Net ist eine symmetrische Encoder-Decoder-Architektur mit horizontalen Skip-Verbindungen, die es ermöglichen, detaillierte Informationen aus frühen Schichten direkt an spätere Schichten des Decoders weiterzugeben.

Im Zusammenhang mit der Zeitreihenanalyse erhält diese Architektur eine neue Bedeutung. Der Encoder komprimiert zeitliche Informationen schrittweise, indem er zunehmend abstraktere Muster extrahiert, während der Decoder die zeitliche Auflösung mithilfe sowohl abstrakter Darstellungen als auch detaillierter Merkmale über Skip-Verbindungen wiederherstellt. Dies ist besonders wichtig bei Finanzdaten, bei denen lokale Schwankungen ebenso bedeutend sein können wie globale Trends.

struct UTransformerNet
{
    NeuralLayer encoder_layers[3];     // Encoder: information compression
    NeuralLayer decoder_layers[3];     // Decoder: restore resolution
    AttentionBlock attention_heads[4]; // Multi-head attention
    double skip_connections[3][32];    // U-Net horizontal connections
    double residuals[6000];            // Residuals for training
    double neural_predictions[6000];   // Neural network predictions
};

Der aus der Transformer-Architektur übernommene Self-Attention-Mechanismus verleiht dem Modell die Fähigkeit, sich dynamisch auf die relevantesten Teile der Eingabesequenz zu konzentrieren. Im Gegensatz zu Faltungs- oder rekurrenten Schichten ermöglicht Attention dem Modell, zeitlich weit auseinanderliegende Ereignisse direkt miteinander in Beziehung zu setzen, was für Finanzmärkte von entscheidender Bedeutung ist, da die Auswirkungen von Ereignissen dort verzögert eintreten können.

void SelfAttention(double &inputs[], AttentionBlock &attention, double &outputs[])
{
    double queries[32], keys[32], values[32];
    
    // Calculate Q, K, V transformations
    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];
        }
    }
    
    // Calculate attention scores: 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); // Scaling
    }
}

Der wesentliche Unterschied der vorgeschlagenen Architektur gegenüber Standard-Transformern besteht darin, dass sie auf eindimensionale Zeitreihen zugeschnitten ist und nicht auf die in Standard-Transformer-Modellen üblicherweise verwendeten Token-Sequenz-Darstellungen. Anstelle der Positionskodierung werden Zeitmerkmale (Stunde des Tages, zyklische Komponenten) verwendet, wodurch das Modell die Intraday-Saisonalität der Handelssitzungen berücksichtigen kann.



Hybridmodell: Lineare Regression plus neuronales Netzwerk

Der Kerngedanke des hybriden Ansatzes ist die additive Zerlegung der Prognose in zwei Komponenten unterschiedlicher Natur:

Final_Prediction = Linear_Model(X) + α × U_Transformer(Residuals)

wobei α ein adaptiver Gewichtungskoeffizient ist, der von der aktuellen Qualität beider Modelle abhängt.

Die lineare Komponente ist als klassisches autoregressives Modell mit einem erweiterten Merkmalsraum aufgebaut:

// Calculate linear prediction
double linear_pred = 0;
for (int i = 0; i < 25; i++)
    linear_pred += g_pair.coeffs[i] * features[i];
Die neuronale Netzkomponente wird nicht anhand der ursprünglichen Kursdaten trainiert, sondern anhand der Residuen des linearen Modells:
void TrainUTransformer()
{
    // Calculate the residuals of a linear model
    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;
    }
    
    // Train a neural network on residuals
    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;
        }
    }
}

Diese Zerlegung bietet mehrere entscheidende Vorteile. Erstens löst das neuronale Netzwerk ein wesentlich einfacheres Problem: die Modellierung von Residuen anstelle der ursprünglichen Zeitreihe. Residuen weisen in der Regel eine geringere Varianz und stärker stationäre statistische Eigenschaften auf, was das Training vereinfacht und das Risiko von Overfitting verringert.

Zweitens implementiert das System auf natürliche Weise das „Fail-Safe“-Prinzip: Wenn die neuronale Netzkomponente beginnt, instabile Prognosen zu erzeugen (hoher Loss), erhöht das System automatisch die Gewichtung der linearen Komponente:

double GetHybridPrediction(double price_t1, double price_t2, double price_t3)
{
    // Linear prediction
    double linear_pred = 0;
    for (int i = 0; i < 25; i++)
        linear_pred += g_pair.coeffs[i] * features[i];
    
    // Neural network correction
    double neural_correction = 0;
    if (g_pair.neural_net.is_trained)
    {
        neural_correction = UTransformerForward(g_pair.coeffs, 0);
    }
    
    // Adaptive weighting
    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;
}

Der Koeffizient neural_weight passt sich automatisch an die Qualität des Modells an: Bei einem hohen Bestimmtheitsmaß R² der linearen Komponente (gute Qualität) erhält die neuronale Netzkomponente ein geringeres Gewicht; bei einem niedrigen Bestimmtheitsmaß R² erhält sie ein höheres Gewicht. Dies gewährleistet die Stabilität des Systems in verschiedenen Marktregimen und verhindert, dass ein schlecht trainiertes neuronales Netzwerk hochwertige lineare Prognosen beeinträchtigt.



Mehrdimensionaler Merkmalsraum und Feature Engineering

Die Qualität eines jeden Modells des maschinellen Lernens hängt entscheidend von der Aussagekraft der Eingabemerkmale ab. Bei der Finanzprognose ist diese Abhängigkeit besonders ausgeprägt, da Marktdaten durch ein hohes Maß an Marktrauschen und Nichtstationarität gekennzeichnet sind. Das vorgeschlagene System nutzt einen sorgfältig konzipierten 25-dimensionalen Merkmalsraum, der sowohl klassische, verzögerte Kurswerte als auch deren nichtlineare Transformationen, technische Indikatoren und zyklische Komponenten umfasst.

Der Basissatz an Merkmalen umfasst direkte und quadratische Terme der verzögerten Kurswerte:

void CalculateFeatures(double price_t1, double price_t2, double price_t3, 
                      int bar_index, string symbol, double &features[])
{
    // Linear price components
    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)
    
    // Difference operators (momentum)
    features[5] = (price_t1 - price_t2);      // Short-term momentum
    features[9] = (price_t1 - price_t3);      // Medium-term momentum
    features[18] = MathPow(price_t1 - price_t2, 2); // Quadratic momentum
}
Zyklische Komponenten bilden periodische Muster auf verschiedenen Zeitskalen ab:
// Low-frequency cycles (daily seasonality)
    features[6] = MathSin(price_t1);
    features[7] = MathCos(price_t1);
    
    // High-frequency cycles (intraday patterns)
    features[13] = MathSin(price_t1 * 1000);
    features[14] = MathCos(price_t1 * 1000);
    
    // Time component (trading session hour)
    datetime bar_time = iTime(symbol, PERIOD_H1, bar_index);
    MqlDateTime dt;
    TimeToStruct(bar_time, dt);
    features[17] = (dt.hour / 24.0);

Nichtlineare Transformationen helfen dem Modell, sich an unterschiedliche Volatilitätsregime anzupassen:

// Root and exponential transformations
    features[15] = MathSqrt(MathAbs(price_t1));
    features[16] = MathExp(-MathAbs(price_t1 - price_t2));
    
    // Hyperbolic tangent for limiting outliers
    features[23] = MathTanh(price_t1 - ma);

Technische Indikatoren liefern zusätzliche Informationen über die Marktmikrostruktur:

// RSI and its nonlinear versions
    double rsi = 50.0; // Simplified calculation for demonstration
    features[10] = (rsi / 100.0);
    features[21] = MathPow(rsi / 100.0, 2);
    
    // Deviation from the moving average
    double ma = price_t1; // Simplified calculation
    features[11] = (price_t1 - ma);
    
    // Volatility (ATR)
    double atr = MathAbs(price_t1 - price_t2);
    features[12] = atr;
    features[20] = (atr * (price_t1 - price_t2)); // Interaction of volatility and momentum

Eine besondere Kategorie bilden Interaktionsmerkmale, die nichtlineare Effekte zwischen verschiedenen Komponenten modellieren:

// Pairwise interactions
    features[8] = (price_t1 * price_t2);
    features[19] = (price_t1 / price_t2);
    
    // Triple interactions
    features[22] = (price_t1 * price_t2 * price_t3);
    
    // Constant term
    features[24] = 1.0;

Dank dieser Vielfalt an Merkmalen kann das lineare Modell ein breites Spektrum an Marktmustern erfassen, von einfachen Trendbewegungen bis hin zu komplexen nichtlinearen Mustern. In diesem Fall wird die neuronale Netzkomponente auf den Residuen dieses reichhaltigen linearen Modells trainiert, was ihre Aufgabe erheblich vereinfacht.



Datenstrukturen und Speicherorganisation in MQL5

Die Implementierung komplexer Algorithmen des maschinellen Lernens in der MQL5-Umgebung erfordert besondere Aufmerksamkeit bei der Organisation von Speicher und Datenstrukturen. Im Gegensatz zu modernen Deep-Learning-Frameworks, die Speicher und Berechnungsgraphen automatisch verwalten, muss der Entwickler in MQL5 effiziente Datenstrukturen manuell entwerfen.

Die zentrale Struktur des Systems, PairData, fasst alle erforderlichen Daten für ein Handelspaar zusammen:

struct PairData
{
    string analyst_symbol;    // Symbol for analysis (EURUSD)
    string trade_symbol;      // Symbol for trading (USDJPY)
    
    // Linear model coefficients
    double coeffs[25];        // Current coefficients
    double best_coeffs[25];   // Best coefficients found
    
    // Quality metrics
    double current_r2;        // Current R²
    double best_r2;           // Best achieved R²
    double learning_rate;     // Adaptive learning rate
    
    // Time series and features
    double prices[6000];                    // Target prices
    double features[6000][50][25];          // Sequences of features
    int data_size;                          // Actual data size
    
    // Position state for averaging/pyramiding
    double last_buy_price;    // Price of the last BUY position
    double last_sell_price;   // Price of the last SELL position
    int buy_levels;           // Number of BUY levels
    int sell_levels;          // Number of SELL levels
    
    // Neural network component
    UTransformerNet neural_net;
};

Die Struktur der neuronalen Schicht ist für eine effiziente Matrixmultiplikation optimiert:

struct NeuralLayer
{
    double weights[64][64];   // Layer weight matrix
    double biases[64];        // Bias vector
    double outputs[64];       // Neuron outputs
    double gradients[64];     // Gradients for backpropagation
    int size;                 // Actual size of the layer
};

Der Attention-Block implementiert eine vereinfachte Version von Multi-Head-Attention:

struct AttentionBlock
{
    double query_weights[32][32];  // Query transformation matrix
    double key_weights[32][32];    // Key transformation matrix  
    double value_weights[32][32];  // Value transformation matrix
    double attention_scores[32];   // Attention weights
    double context[32];            // Context vector
};

Ein wesentliches Merkmal der Speicherorganisation ist die Verwendung statischer Arrays fester Größe anstelle dynamischer Strukturen. Dies liegt daran, dass ein vorhersehbarer Speicherverbrauch gewährleistet werden muss:

// Maximum array sizes are specified as constants
double prices[6000];                    // Maximum 6000 historical points
double features[6000][50][25];          // Sequences of 50 bars
double residuals[6000];                 // Residuals for training the neural network

Das dreidimensionale Array features[6000][50][25] verdient besondere Beachtung. Die erste Dimension entspricht historischen Datenpunkten, die zweite Zeitsequenzen der Länge 50 (zur möglichen Verwendung in rekurrenten oder Attention-Mechanismen) und die dritte 25 Merkmalen. Diese Organisation ermöglicht einen effizienten Zugriff auf Daten für Training und Prognose.

Zur Initialisierung der Gewichte des neuronalen Netzes wird die Xavier/Glorot-Initialisierung verwendet, um ein stabiles Training zu gewährleisten:

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;
            }
        }
    }
}

Diese Speicherorganisation gewährleistet einen effizienten Systembetrieb auch bei begrenzten Ressourcen der Handelsplattform und ermöglicht gleichzeitig die Implementierung komplexer Algorithmen des maschinellen Lernens.



Lernalgorithmus des Zwei-Komponenten-Systems

Das Training eines Hybridsystems ist ein iterativer Prozess, bei dem die lineare und die neuronale Netzkomponente abwechselnd, jedoch koordiniert optimiert werden. Dieser Ansatz gewährleistet eine stabile Konvergenz und ermöglicht es jeder Komponente, sich auf ihren jeweiligen Teil des Prognoseproblems zu spezialisieren.

Der erste Schritt besteht darin, das lineare Modell mithilfe von Gradientenabstieg mit adaptiver Lernrate zu optimieren:

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);
        
        // Calculate gradients for all training examples
        for (int i = 0; i < g_pair.data_size; i++)
        {
            double actual = g_pair.prices[i];
            double predicted = 0.0;
            
            // Forward pass of the linear model
            for (int j = 0; j < 25; j++)
                predicted += g_pair.coeffs[j] * g_pair.features[i][0][j];
            
            double error = predicted - actual;
            
            // Accumulate gradients: ∂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];
        }
        
        // Normalize gradients
        for (int j = 0; j < 25; j++)
            gradients[j] /= g_pair.data_size;

Ein entscheidendes Element ist Gradient Clipping, um explodierende Gradienten zu verhindern:

// Gradient clipping for stability
        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;
        }
        
        // Update coefficients
        for (int j = 0; j < 25; j++)
            g_pair.coeffs[j] -= g_pair.learning_rate * gradients[j];

Eine adaptive Lernrate passt sich automatisch an die Optimierungsdynamik an:

double new_r2 = CalculateR2();
        
        if (new_r2 > best_r2)
        {
            // Improvement found - increasing learning rate
            best_r2 = new_r2;
            ArrayCopy(best_coeffs, g_pair.coeffs);
            g_pair.learning_rate *= 1.01;
        }
        else
        {
            // Deterioration - roll back and reduce the rate
            ArrayCopy(g_pair.coeffs, best_coeffs);
            g_pair.learning_rate *= 0.8;
            
            if (g_pair.learning_rate < InitialLearningRate * 0.01)
                break;// Too low speed - stop
        }
    }
}

Die zweite Phase besteht darin, das neuronale Netzwerk auf den Residuen des optimierten linearen Modells zu trainieren:

void TrainUTransformer()
{
    // Calculate residuals after optimizing a linear model
    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++)
        {
            // Forward pass neural networks
            double prediction = UTransformerForward(g_pair.coeffs, 
                                                   g_pair.neural_net.residuals[i]);
            
            // Calculate MSE loss
            double error = prediction - g_pair.neural_net.residuals[i];
            total_loss += error * error;
            
            // Simplified backpropagation (gradient by last layer)
            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 to prevent overfitting
        if (total_loss < best_loss)
        {
            best_loss = total_loss;
            no_improve_count = 0;
        }
        else
        {
            no_improve_count++;
            if (no_improve_count > 5) break;
        }
    }
}

Die Koordination des Lernprozesses der beiden Komponenten wird durch regelmäßige Reoptimierung erreicht:

if (g_pair.bars_since_optimization >= OptimizationInterval)
{
    PrepareOptimizationData();
    OptimizeCoefficients();        // Linear model first
    
    // Retrain the neural network every 5 optimization cycles
    if (g_pair.neural_net.training_steps % 5 == 0)
    {
        TrainUTransformer();       // Then the neural network on the new residues
    }
    
    g_pair.bars_since_optimization = 0;
}



Verfahren für den Forward Pass durch den U-Transformer

Der Forward Pass durch den U-Transformer ist die sequenzielle Verarbeitung von Eingabedaten in einer Encoder-Decoder-Architektur mit integrierten Attention-Mechanismen. Das Verfahren beginnt mit der Vorbereitung eines Eingabevektors, der über die Zeitsequenz gemittelte Merkmale enthält:

double UTransformerForward(double &coefficients[], double residual)
{
    double layer_input[32];
    double layer_output[32];
    double attention_output[32];
    
    // Input data preparation: averaging features over a sequence
    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;
    }
    
    // Initialize the input layer
    for (int i = 0; i < 25; i++)
        layer_input[i] = avg_features[i];

Der Encoder-Teil der Architektur verarbeitet Informationen sequenziell durch mehrere Schichten, von denen jede auf die Extraktion von Merkmalen auf einer jeweils anderen Abstraktionsebene spezialisiert ist:

// Pass through encoder layers while preserving skip connections
    for (int layer = 0; layer < NeuralLayers; layer++)
    {
        int input_size = (layer == 0) ? 25 : NeuralNodes;
        
        // Forward pass via a fully connected layer
        ForwardLayer(layer_input, input_size, 
                    g_pair.neural_net.encoder_layers[layer], layer_output);
        
        // Apply self-attention (for the first layers)
        if (layer < TransformerHeads)
        {
            SelfAttention(layer_output, 
                         g_pair.neural_net.attention_heads[layer], 
                         attention_output);
            
            // Residual connection: output = layer + attention
            for (int i = 0; i < NeuralNodes; i++)
                layer_output[i] = layer_output[i] + attention_output[i];
        }
        
        // Save skip connection for decoder
        for (int i = 0; i < NeuralNodes; i++)
            g_pair.neural_net.skip_connections[layer][i] = layer_output[i];
        
        // Prepare the input for the next layer
        for (int i = 0; i < NeuralNodes; i++)
            layer_input[i] = layer_output[i];
    }

Die vollständig verbundene Schicht implementiert die standardmäßige lineare Transformationsoperation mit nichtlinearer Aktivierung:

void ForwardLayer(double &inputs[], int input_size, NeuralLayer &layer, double &outputs[])
{
    for (int j = 0; j < layer.size; j++)
    {
        double sum = layer.biases[j];
        
        // Matrix multiplication: W * x + b
        for (int i = 0; i < input_size; i++)
            sum += inputs[i] * layer.weights[i][j];
        
        // GELU activation for better convergence
        outputs[j] = GELU(sum);
    }
}

GELU (Gaussian Error Linear Unit) wird als moderne Alternative zu ReLU verwendet:

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

Der Self-Attention-Mechanismus berechnet Attention-Gewichte für verschiedene Teile der Eingabesequenz:

void SelfAttention(double &inputs[], AttentionBlock &attention, double &outputs[])
{
    double queries[32], keys[32], values[32];
    
    // Calculate Query, Key, Value vectors
    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];
        }
    }
    
    // Softmax normalization of attention weights
    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;
    
    // Apply attention weights to values
    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];
    }
}

Bei der abschließenden Aggregation wird Average Pooling verwendet, um einen skalaren Ausgabewert zu erzeugen:

// Decoder part (simplified - uses only the last encoder output)
    double final_sum = 0;
    for (int i = 0; i < NeuralNodes; i++)
        final_sum += layer_output[i];
    
    return final_sum / NeuralNodes; // Average pooling
}

Diese Architektur ermöglicht eine mehrstufige Informationsverarbeitung: Low-Level-Schichten extrahieren lokale Muster, Attention-Mechanismen modellieren langfristige Abhängigkeiten, und Skip-Verbindungen speichern detaillierte Informationen für die abschließende Prognose.



System zur Erzeugung von Handelssignalen

Bei der Generierung von Handelssignalen im Hybridsystem kommt eine zweistufige Architektur zum Einsatz, bei der je nach Qualität der einzelnen Komponenten automatisch zwischen den Signalquellen umgeschaltet wird. Diese Architektur gewährleistet die Robustheit des Systems gegenüber verschiedenen Marktregimen und verhindert Leistungseinbußen, wenn die Qualität eines der Modelle nachlässt.

Die primäre Signalquelle ist der U-Transformer, der aktiviert wird, sobald eine ausreichende Trainingsqualität erreicht ist:

void ProcessPair()
{
    // Obtain data for analysis from the analyst symbol
    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);
    
    // Current price of the trading symbol
    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;
    
    // MAIN SIGNAL: U-Transformer (at high quality)
    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;      // BUY signal
        else if (neural_prediction < -neural_threshold) signal = -1; // SELL signal
    }
}

Die Backup-Signalquelle wird aktiviert, wenn das neuronale Netzwerk noch nicht trainiert wurde oder unbefriedigende Ergebnisse zeigt:

// BACKUP SIGNAL: Linear Model
    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;
    }

Ein kritisches Element ist der adaptive Reaktionsschwellenwert, der abhängig von der Qualität des Modells skaliert. Bei Signalen aus neuronalen Netzen wird ein fester Schwellenwert verwendet, da das Modell auf normalisierten Residuen trainiert wird. Bei linearen Signalen ist der Schwellenwert proportional zum Bestimmtheitsmaß R² des Modells – je höher die Anpassungsqualität, desto aggressivere Signale kann das System erzeugen.

Die Protokollierung der Signalquelle gewährleistet Transparenz bei Handelsentscheidungen:

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));
}

Durch die Trennung von Symbolen für Analyse und Handel ist es möglich, den Handel mit synthetischen Symbolen (zum Beispiel Renko-Bars) zu praktizieren. Beispielsweise kann eine Renko-Analyse des EURUSD-Paares Signale für den Handel mit EURUSD liefern, was die Leistungsfähigkeit des Systems erweitert und durch die Nutzung zusätzlicher Informationen potenziell die Qualität der Signale verbessert.



Positionsmanagement- und Risikomanagementstrategien

Das Positionsmanagementsystem setzt zwei sich ergänzende Strategien um: Averaging (Aufstocken unprofitabler Positionen) und Pyramiding (Ausbau profitabler Positionen). Diese Strategien passen sich automatisch an die Marktbedingungen an und sind in ein System zur Signalerzeugung integriert.

Die Struktur zur Positionsverfolgung umfasst alle notwendigen Parameter zur Umsetzung komplexer Strategien:

struct PairData
{
    // Track the status of positions
    double last_buy_price;      // Price of the last BUY position
    double last_sell_price;     // Price of the last SELL position
    int buy_levels;             // Number of BUY position levels
    int sell_levels;            // Number of levels of SELL positions
    bool last_was_averaging;    // Flag of the last averaging operation
};

Die Averaging-Strategie wird aktiviert, wenn sich der Kurs um einen festgelegten Abstand entgegen der offenen Position bewegt und das System weiterhin Signale in derselben Richtung generiert:

// AVERAGING BUY positions
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");
        }
    }
}

Das Pyramiding funktioniert genau umgekehrt – es stockt profitable Positionen auf:

// PYRAMIDING BUY positions
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");
    }
}

Ein wesentliches Merkmal der Implementierung ist das Verbot der gleichzeitigen Anwendung von Averaging und Pyramiding (das Flag „last_was_averaging“), wodurch verhindert wird, dass die Strategie zu einer chaotischen Anhäufung von Positionen ausartet.

Beim Lotgrößenmanagement werden für verschiedene Arten von Operationen Multiplikatoren verwendet:

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;
}

Diese Architektur für das Positionsmanagement bietet Flexibilität im Positionsmanagement und gewährleistet gleichzeitig die Kontrolle über das maximale Risiko. Das System kann sich an verschiedene Marktbedingungen anpassen, indem es in Trendmärkten Averaging und bei Impulsbewegungen Pyramiding einsetzt, wobei klar definierte Regeln für die Gewinnmitnahme gelten.


Automatische Reoptimierung der Modellkoeffizienten

Die Anpassungsfähigkeit an sich ändernde Marktbedingungen ist eine entscheidende Voraussetzung für jedes Handelssystem. Finanzmärkte weisen ein nichtstationäres Verhalten auf, bei dem sich statistische Muster aufgrund makroökonomischer Ereignisse, Veränderungen in der Marktmikrostruktur oder Verschiebungen im Verhalten der Marktteilnehmer ändern können. Das System implementiert eine automatische Reoptimierung, die die Modellparameter regelmäßig auf der Grundlage eines gleitenden Fensters historischer Daten aktualisiert.

Der Auslöser für die Reoptimierung ist die Ansammlung einer festgelegten Anzahl neuer Bars:

void OnTick()
{
    if (!g_initialized) return;
    
    ProcessPair();
    
    // Check the need for reoptimization
    if (isNewBar())
    {
        if (g_pair.bars_since_optimization >= OptimizationInterval)
        {
            PrepareOptimizationData();      // Data preparation
            OptimizeCoefficients();         // Re-optimization of the linear model
            
            // Retrain the neural network every 5 cycles
            if (g_pair.neural_net.training_steps % 5 == 0)
            {
                TrainUTransformer();
            }
            
            g_pair.bars_since_optimization = 0;
        }
    }
}

Bei der Datenaufbereitung für die Optimierung wird ein gleitendes Fenster mit fester Größe verwendet, das ein Gleichgewicht zwischen Modellstabilität und Anpassungsfähigkeit an Veränderungen gewährleistet:

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;
        
        // Collection of time sequences of features
        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");
}

Das Optimierungsverfahren umfasst einen Mechanismus zum Speichern der besten ermittelten Koeffizienten:

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();
    
    // Improvement criterion for continuing optimization
    double initial_r2 = g_pair.current_r2;
    
    for (int iter = 0; iter < MaxIterations; iter++)
    {
        // ... gradient descent code ...
        
        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; // Acceleration when improving
        }
        else
        {
            ArrayCopy(g_pair.coeffs, best_coeffs);
            g_pair.learning_rate *= 0.8;  // Slowdown when deteriorating
            
            if (g_pair.learning_rate < InitialLearningRate * 0.01)
                break;
        }
        
        // Stop criterion for minimum improvement
        if (iter > 10 && (best_r2 - initial_r2) < MinR2Improvement)
            break;
    }
    
    // Update the best coefficients
    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));
}

Das System überwacht die Qualität des Modells anhand des Bestimmtheitsmaßes R², das den Anteil der erklärten Varianz angibt:

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;
    
    // Calculate the average value
    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;
    
    // Calculate R² components
    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);
}



Online-Training eines neuronalen Netzes auf Residuen

Das Online-Training einer neuronalen Netzwerkkomponente ist im Vergleich zur linearen Optimierung eine komplexere Aufgabe. Das neuronale Netz wird nicht bei jedem Optimierungszyklus neu trainiert, sondern periodisch – alle 5 Zyklen –, wodurch Overfitting verhindert und die Rechenlast verringert wird.

void TrainUTransformer()
{
    if (g_pair.data_size < 10)
    {
        Print("Insufficient data for U-Transformer training: ", g_pair.data_size);
        return;
    }
    
    // Calculate residuals from the optimized linear model
    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;

Das Trainingsverfahren nutzt eine vereinfachte Version der Backpropagation, die an die Einschränkungen von MQL5 angepasst ist:

for (int epoch = 0; epoch < NeuralEpochs; epoch++)
    {
        double total_loss = 0;
        
        for (int i = 0; i < g_pair.data_size; i++)
        {
            // Forward pass
            double prediction = UTransformerForward(g_pair.coeffs, 
                                                   g_pair.neural_net.residuals[i]);
            g_pair.neural_net.neural_predictions[i] = prediction;
            
            // Calculate MSE loss
            double error = prediction - g_pair.neural_net.residuals[i];
            total_loss += error * error;
            
            // Simplified backpropagation
            double gradient = 2.0 * error / g_pair.data_size;
            
            // Update weights (simplified scheme)
            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 mechanism
        if (total_loss < best_loss)
        {
            best_loss = total_loss;
            no_improve_count = 0;
        }
        else
        {
            no_improve_count++;
            if (no_improve_count > 5) break; // Stop if there are no improvements
        }
        
        if (epoch % 5 == 0)
            Print("U-Transformer epoch ", epoch, " loss: ", DoubleToString(total_loss, 6));
    }

Zum Abschluss des Trainings werden die Metriken und der Zustand des neuronalen Netzes aktualisiert:

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));
}

Das Qualitätskriterium für das neuronale Netz (Loss < 0,01) legt fest, wann sich das System auf die Signale des neuronalen Netzes verlassen kann:

// In the signal generation function
if (g_pair.neural_net.is_trained && g_pair.neural_net.loss < 0.01)
{
    // Use U-Transformer to generate signals
    double neural_prediction = UTransformerForward(g_pair.coeffs, 0);
    // ...
}

Diese Online-Lernarchitektur ermöglicht es der neuronalen Netzwerkkomponente, sich an veränderte Marktbedingungen anzupassen und dabei die Recheneffizienz aufrechtzuerhalten sowie Overfitting zu verhindern.

Der EA aus dem Artikel über Regressionsmodelle diente als Grundlage und wurde um das Hybridmodell ergänzt.

Betrachten wir die Performance des Modells für EURUSD M15 im Zeitraum ab dem 1. Juli 2025:

Die Sharpe-Ratio fiel recht gut aus:


Fazit

Das vorgestellte Hybridsystem belegt die praktische Machbarkeit, moderne Deep-Learning-Architekturen mit klassischen ökonometrischen Methoden innerhalb der Beschränkungen der MQL5-Handelsplattform zu integrieren. Durch eine zweistufige Zerlegung des Prognoseproblems in lineare und nichtlineare Komponenten lassen sich die Stärken beider Ansätze nutzen und gleichzeitig ihre Schwächen minimieren.

Zu den wichtigsten Ergebnissen dieser Arbeit zählen die erfolgreiche Anpassung der U-Net-Architektur an die Analyse finanzieller Zeitreihen, die Integration von Attention-Mechanismen zur Modellierung langfristiger Abhängigkeiten sowie die Implementierung eines automatischen Umschaltsystems zwischen Signalquellen. Dank einer adaptiven Gewichtung der Komponenten und einer periodischen Reoptimierung der Parameter zeigt das System einen stabilen Betrieb unter verschiedenen Marktbedingungen.

Der praktische Wert der Lösung liegt in ihrer Einsatzbereitschaft für den realen Handel. Das System umfasst eine vollständige Handelslogik mit Positionsmanagement, Risikomanagement und detaillierter Transaktionsprotokollierung. Die Trennung von Symbolen für Analyse und Handel erweitert die Möglichkeiten zur Nutzung von Intermarket-Korrelationen.

Allerdings sollten die Einschränkungen der derzeitigen Umsetzung ehrlich eingeräumt werden. Eine vereinfachte Backpropagation, das Fehlen moderner Regularisierungstechniken und eine feste Netzwerkarchitektur schränken das Potenzial der neuronalen Netzwerkkomponente ein. Die Verwendung statischer Arrays anstelle dynamischer Datenstrukturen schränkt die Skalierbarkeit des Systems erheblich ein.

Dennoch eröffnet der vorgeschlagene Ansatz eine vielversprechende Richtung für die Entwicklung von Handelssystemen. Das Konzept, ein neuronales Netzwerk anhand der Residuen eines linearen Modells zu trainieren, lässt sich auf eine breitere Klasse von Prognoseproblemen anwenden und ist nicht auf Finanzmärkte beschränkt. Die modulare Architektur des Systems ermöglicht es, problemlos mit verschiedenen Arten von neuronalen Netzen und Strategien für das Positionsmanagement zu experimentieren.

Von besonderem Wert ist der Nachweis, dass fortschrittliche Techniken des maschinellen Lernens sogar in eingeschränkten Entwicklungsumgebungen wie MQL5 implementiert werden können. Dies eröffnet Möglichkeiten für eine Vielzahl von Händlern und Entwicklern, die keinen Zugang zu modernen Deep-Learning-Frameworks haben.


Literaturverzeichnis

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/18916

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Stanislav Korotky
Stanislav Korotky | 22 Aug. 2025 in 13:18
Im Bilanzdiagramm gibt es keine Mittelwertbildung/Pyramidenbildung, wie behauptet wurde, da hierfür ein sägezahnförmiges Histogramm der Einzahlungsentwicklung vorliegen müsste.
Neuronale Netze im Handel: Probabilistische Zeitreihenprognose (Encoder) Neuronale Netze im Handel: Probabilistische Zeitreihenprognose (Encoder)
Wir laden Sie ein, einen neuen Ansatz kennenzulernen, der klassische Methoden und moderne neuronale Netze für die Zeitreihenanalyse kombiniert. Der Artikel enthält eine ausführliche Erläuterung der Architektur und der Funktionsweise des K²VAE-Modells.
Neuronale Netze im Trading: Probabilistische Zeitreihenprognose (K²VAE) Neuronale Netze im Trading: Probabilistische Zeitreihenprognose (K²VAE)
Wir laden Sie ein, die ursprüngliche Implementierung des K²VAE-Frameworks zu erkunden – ein flexibles Modell, das komplexe Dynamiken im latenten Raum linear annähern kann. Dieser Artikel zeigt, wie man wichtige Komponenten in MQL5 implementiert, einschließlich parametrisierter Matrizen sowie deren Verwaltung außerhalb der Standardschichten neuronaler Netze. Dieses Material ist für alle hilfreich, die nach einem praktischen Ansatz zur Erstellung interpretierbarer Zeitreihenmodelle suchen.
Analyse der stündlichen Kursentwicklung von Symbolen und ihrer Spreads in MetaTrader 5 Analyse der stündlichen Kursentwicklung von Symbolen und ihrer Spreads in MetaTrader 5
Der Indikator ProSpread-Saisonalitätsindex mit gleitendem Durchschnitt ist ein Werkzeug der technischen Analyse, das saisonale Muster in Kursbewegungen erkennt, das Kursverhalten während bestimmter Handelszeiten analysiert und sowohl mit einem einzelnen Instrument als auch mit einem Spread zwischen zwei Vermögenswerten arbeiten kann. Außerdem visualisiert er die statistische Wahrscheinlichkeit gerichteter Bewegungen.
Neuronale Netze im Trading: Adaptive periodische Segmentierung (Fazit) Neuronale Netze im Trading: Adaptive periodische Segmentierung (Fazit)
Wir laden Sie ein, in die spannende Welt von LightGTS einzutauchen – einem schlanken und dennoch leistungsstarken Framework für die Prognose von Zeitreihen, bei dem adaptive Faltung und RoPE-Kodierung mit innovativen Aufmerksamkeitsmechanismen kombiniert werden. In unserem Artikel finden Sie eine detaillierte Beschreibung aller Komponenten – von der Erstellung von Patches bis hin zur komplexen Mixture of Experts im Decoder –, die zur Integration in MQL5-Projekte bereit sind. Entdecken Sie, wie LightGTS den automatisierten Handel auf ein ganz neues Niveau hebt!