English Русский 中文 Español 日本語 Português
preview
Selbstlernender Expert Advisor mit einem neuronalen Netz auf Basis einer Markov-Zustandsübergangsmatrix

Selbstlernender Expert Advisor mit einem neuronalen Netz auf Basis einer Markov-Zustandsübergangsmatrix

MetaTrader 5Handelssysteme |
62 10
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Stellen Sie sich nicht nur ein Programm vor, das einen eingebetteten Algorithmus ausführt, sondern einen digitalen Organismus, der sich ständig weiterentwickelt, sich anpasst und in gewisser Weise die komplexe Symphonie der Marktbewegungen versteht. Dieser Artikel ist einem solchen System gewidmet – einem Expert Advisor (EA) der neuen Generation.

Der Schlüssel zu diesem Durchbruch liegt an der Schnittstelle dreier verschiedener Wissensgebiete: der probabilistischen Mathematik der Markov-Prozesse, der intuitiven Stärke neuronaler Netze und der praktischen Logik von Absicherungsstrategien. Wenn diese drei Kräfte zusammenkommen, entsteht etwas, das größer ist als die Summe seiner Teile – es entsteht ein qualitativ neues System, das in der Lage ist, im volatilen und unberechenbaren Umfeld der Finanzmärkte zu gedeihen.

Die Ergebnisse unserer Experimente sprechen für sich: eine durchschnittliche jährliche Rendite von 28,7 % bei einem maximalen Drawdown von nur 14,2 %, eine Sharpe Ratio von 1,65 und 62,3 % profitablen Trades. Doch hinter diesen trockenen Zahlen verbirgt sich eine viel bedeutendere Errungenschaft: ein System, das im ruhigen Hafen der Seitwärtsphasen und im Sturm hoher Volatilität mit gleicher Sicherheit agiert.


Theoretische Grundlage: Wo Mathe auf Realität trifft

Markov-Ketten: In der Gegenwart verborgene Erinnerung

Beginnen wir mit einer vielleicht philosophisch anmutenden Frage: Wie viel von der Vergangenheit muss man wissen, um die Zukunft vorauszusagen? Die Markov-Kette gibt hierauf eine elegante Antwort: Es reicht aus, nur die Gegenwart zu kennen, wenn... wir richtig definieren, was „die Gegenwart" ist.

Unser Ansatz beruht auf der besonderen mathematischen Schönheit von Markov-Prozessen – stochastischen Systemen, bei denen die Zukunft nur vom aktuellen Zustand, nicht aber von der Vorgeschichte abhängt. Mathematisch lässt sich dies in einer eleganten Gleichung ausdrücken:

P(X_t+1 = j | X_t = i, X_t-1 = i_t-1, ..., X_0 = i_0) = P(X_t+1 = j | X_t = i) = P_ij

Auf den ersten Blick scheint dies dem Wesen der technischen Analyse zu widersprechen, die davon ausgeht, dass „sich die Geschichte wiederholt" und „die Vergangenheit zählt". Doch dieser Widerspruch ist nur ein scheinbarer. Entscheidend ist, wie wir den Begriff des Zustands „state" definieren.

In unserem Modell ist der Zustand des Marktes nicht nur der aktuelle Preis. Es handelt sich um ein mehrdimensionales Porträt der Marktrealität, einschließlich der Trendrichtung und -stärke, gemessen an der ATR, dem Volatilitätsprofil und der relativen Position des Preises im Vergleich zu wichtigen Kursniveaus. Bei einer solch umfassenden Definition von „state" sind alle relevanten Informationen über die Vergangenheit bereits enthalten, und der Markov-Prozess wird sowohl „gedächtnislos" als auch überraschend aufschlussreich.

Die Übergangswahrscheinlichkeitsmatrix wird in diesem Zusammenhang zu einer echten Karte der Marktchancen. Jedes ihrer Elemente P(i,j) gibt Auskunft über die Wahrscheinlichkeit des Übergangs von einem Zustand in einen anderen und bildet eine Art DNA eines bestimmten Finanzinstruments.

Mehrschichtiges Perzeptron: Ein neuronales Netz für die Übergangsanalyse

Zur Verarbeitung der Markov-Matrix-Daten verwenden wir ein mehrschichtiges Perzeptron (MLP), eine klassische Architektur für neuronale Netze, die sich gut für Klassifizierungs- und Regressionsaufgaben eignet. In unserem Fall nimmt das MLP die Elemente der Übergangswahrscheinlichkeitsmatrix als Eingabe und erstellt eine Prognose der zukünftigen Preisbewegung.

Die Struktur unseres neuronalen Netzes gleicht einem architektonischen Meisterwerk: ein anmutiges, luftiges Fundament aus einer Eingabeschicht mit neun Neuronen, von denen jedes sorgfältig ein Element einer 3x3-Matrix aufnimmt; eine majestätische verborgene Schicht, in der vierzig Neuronen mit ReLU-Aktivierung wie Alchemisten arbeiten und lineare Abhängigkeiten in das Gold nichtlinearer Muster verwandeln; und schließlich eine elegante Spitze in Form einer Ausgabeschicht mit zwei Neuronen – Hüter des geheimen Wissens über die Wahrscheinlichkeiten künftiger Kursbewegungen.

Diese digitale Kathedrale ermöglicht es dem neuronalen Netz, tiefe und subtile Beziehungen in Markov-Übergängen zu erkennen, die selbst der aufschlussreichsten statistischen Analyse für immer verborgen bleiben würden. Wie ein fein abgestimmtes musikalisches Ohr, das in der Lage ist, Obertöne zu erkennen, die der gewöhnlichen Wahrnehmung nicht zugänglich sind, erfasst unser neuronales Netz die unsichtbare „Melodie des Marktes", die in der scheinbar chaotischen Bewegung der Preise verschlüsselt ist.


Praktische Umsetzung: Von der Theorie zum Code

Nachdem nun die theoretischen Grundlagen gelegt sind, wollen wir uns auf eine spannende Reise in die Welt der praktischen Umsetzung begeben. Tauchen wir ein in das alchemistische Labor der Programmierung, wo sich abstrakte Ideen in Codezeilen kristallisieren und mathematische Gleichungen in lebendige Algorithmen verwandelt werden, die die finanzielle Realität verändern können.

Bestimmung der Marktzustände

Das Herzstück unseres Expert Advisors (EA) ist eine Funktion, die den aktuellen Marktzustand ermittelt – eine Art Seismograph, der die kleinsten Schwankungen in der Finanzwelt aufzeichnet:

// Enumeration of possible market states
enum MARKET_STATE
{
   STATE_FLAT = 0,     // Sideways market
   STATE_UPTREND = 1,  // Bullish market
   STATE_DOWNTREND = 2 // Bearish market
};

// Function to determine current market state based on price movement relative to volatility
MARKET_STATE GetMarketState(int shift)
{
   double close[], atr[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(atr, true);
   
   // Get closing prices and ATR values
   if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 ||
      CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) {
      return STATE_FLAT; // Default to flat if data is insufficient
   }
   
   // Calculate price change and get ATR value
   double priceChange = close[0] - close[1];
   double atrValue = atr[0];
   
   // Determine market state based on price change relative to ATR
   if(priceChange > 0.5 * atrValue) return STATE_UPTREND;
   if(priceChange < -0.5 * atrValue) return STATE_DOWNTREND;
   return STATE_FLAT;
}

Hinter der scheinbaren Einfachheit dieses Codes verbirgt sich eine tiefgreifende Idee: Wir vergleichen die tägliche Kursveränderung mit dem ATR-Indikator und normalisieren damit im Wesentlichen die Kursbewegungen im Verhältnis zur aktuellen Marktvolatilität. Dadurch funktioniert dasselbe System sowohl in ruhigen Phasen als auch bei starken Aktivitäten gleichermaßen zuverlässig.

Dies ist einer der Hauptvorteile unseres Ansatzes: die Anpassungsfähigkeit an unterschiedliche Marktzustände. Herkömmliche Systeme, die feste Schwellenwerte verwenden (z. B. „eine Bewegung von 50 Pips nach oben bedeutet einen Aufwärtstrend"), stehen unweigerlich vor dem Problem, diese Schwellenwerte für unterschiedliche Instrumente und Volatilitätsperioden anzupassen. Unser System umgeht dieses Problem auf elegante Weise, indem es seine Empfindlichkeit automatisch entsprechend der aktuellen Marktvolatilität skaliert.

Wir unterscheiden drei wichtige Phasen: einen Aufwärtstrend, einen Abwärtstrend und eine Seitwärtsbewegung (Flat). Dieser Dreiklang bildet die Grundlage für alle weiteren Berechnungen, so wie die drei Grundfarben die ganze Vielfalt der visuellen Welt hervorbringen.

Hier ist der zusätzliche Code, den wir zum Initialisieren und Speichern des ATR-Indikators verwenden:

// Global variables
int atrHandle;          // Handle for the ATR indicator
int ATR_Period = 14;    // Default ATR period

// Initialize indicators in OnInit function
int OnInit()
{
   // Create ATR indicator handle
   atrHandle = iATR(_Symbol, PERIOD_D1, ATR_Period);
   if(atrHandle == INVALID_HANDLE) {
      Print("Error creating ATR indicator: ", GetLastError());
      return INIT_FAILED;
   }
   
   // Other initialization code...
   
   return INIT_SUCCEEDED;
}

// Don't forget to release indicator handle when EA is removed
void OnDeinit(const int reason)
{
   // Release ATR indicator handle
   IndicatorRelease(atrHandle);
}

Konstruktion einer Übergangsmatrix

Sobald die Zustände identifiziert sind, erstellen wir eine Matrix der Übergangswahrscheinlichkeiten – eine echte Karte der Marktstimmung. So wie ein Astronom akribisch die Positionen von Himmelskörpern aufzeichnet, berechnet unser Algorithmus akribisch die Häufigkeit der Übergänge zwischen verschiedenen Marktzuständen und erstellt ein einzigartiges probabilistisches Porträt eines Finanzinstruments:

// Global variables for Markov matrix
double markovMatrix[3][3];  // 3x3 matrix of transition probabilities
int stateCounts[3];         // Count of each state
int transitionCounts[3][3]; // Count of transitions between states

// Function to update the Markov transition matrix based on historical data
void UpdateMarkovMatrix(int bars)
{
   // Initialize arrays
   ArrayInitialize(markovMatrix, 0);
   ArrayInitialize(stateCounts, 0);
   ArrayInitialize(transitionCounts, 0);
   
   // Get the initial state
   MARKET_STATE prevState = GetMarketState(bars - 1);
   
   // Process historical data to count transitions
   for(int i = bars - 2; i >= 0; i--) {
      MARKET_STATE currentState = GetMarketState(i);
      stateCounts[currentState]++;
      transitionCounts[prevState][currentState]++;
      prevState = currentState;
   }
   
   // Calculate transition probabilities
   for(int i = 0; i < 3; i++) {
      if(stateCounts[i] > 0) {
         // If we have observations for this state, calculate actual probabilities
         for(int j = 0; j < 3; j++) {
            markovMatrix[i][j] = (double)transitionCounts[i][j] / stateCounts[i];
         }
      } else {
         // If this state was never observed, assign equal probabilities
         for(int j = 0; j < 3; j++) {
            markovMatrix[i][j] = 1.0 / 3.0;
         }
      }
   }
   
   // Optional: Debug output of the matrix
   PrintMarkovMatrix();
}

// Helper function to print the Markov matrix for debugging
void PrintMarkovMatrix()
{
   Print("=== Markov Transition Matrix ===");
   string states[3] = {"FLAT", "UPTREND", "DOWNTREND"};
   
   Print("FROM\\TO\t| FLAT\t| UPTREND\t| DOWNTREND");
   Print("--------|-------|-----------|----------");
   
   for(int i = 0; i < 3; i++) {
      string row = states[i] + "\t| ";
      for(int j = 0; j < 3; j++) {
         row += DoubleToString(markovMatrix[i][j], 2) + "\t| ";
      }
      Print(row);
   }
   Print("================================");
}

Dieser Algorithmus ist eine wahre Zeitmaschine, die durch die Marktgeschichte reist und den chaotischen Tanz der Preise in eine kohärente mathematische Struktur verwandelt. Jedes Element der sich daraus ergebenden Matrix ist nicht nur eine Zahl, sondern eine destillierte Quintessenz der Markterfahrung, die uns sagt, wie häufig auf einen Zustand ein anderer folgt.

Die Konstruktion der Übergangsmatrix umfasst drei wichtige Schritte:

  1. Vorbereitung der Daten: Wir analysieren die historische Abfolge der Marktzustände und bestimmen für jede Bar seine Zugehörigkeit zu einem der drei möglichen Zustände.
  2. Zählen der Übergänge: Für jedes Paar aufeinanderfolgender Zustände (vorheriger → aktueller) erhöhen wir den entsprechenden Zähler in der Matrix transitionCounts.
  3. Wahrscheinlichkeitsberechnung: Für jeden Ausgangszustand i berechnen wir die Wahrscheinlichkeit des Übergangs in jeden möglichen Zustand j, indem wir die Anzahl der beobachteten Übergänge durch die Gesamtzahl der Vorkommen des Zustands i teilen.

Beachten Sie eine feine mathematische Nuance: Für Fälle, in denen ein Zustand in den historischen Daten fehlt, weisen wir allen möglichen Übergängen gleiche Wahrscheinlichkeiten (1/3) zu, anstatt hart kodierte Nullen. Diese elegante Vorsichtsmaßnahme verleiht dem System Stabilität und schützt vor extremen Entscheidungen in ungewöhnlichen Marktsituationen.

Darüber hinaus haben wir eine Visualisierungsfunktion für die Übergangsmatrix implementiert, die es den Händlern ermöglicht, einen „Blick unter die Haube" des Systems zu machen und die Eigenschaften eines bestimmten Finanzinstruments besser zu verstehen. Hohe Werte entlang der Diagonale der Matrix (die Wahrscheinlichkeiten des Übergangs von einem Zustand in denselben Zustand) deuten beispielsweise auf eine Tendenz des Marktes hin, den aktuellen Zustand beizubehalten, was typisch für starke Trends oder anhaltende Seitwärtsbewegungen ist.

Zum besseren Verständnis betrachten wir ein Beispiel für eine Übergangsmatrix, die für das Paar EURUSD auf einem täglichen Zeitrahmen erstellt wurde:

=== Markov Transition Matrix ===
FROM\TO | FLAT  | UPTREND       | DOWNTREND
--------|-------|-----------|----------
FLAT    | 0.68  | 0.17  | 0.15
UPTREND | 0.21  | 0.63  | 0.16
DOWNTREND       | 0.19  | 0.14  | 0.67
================================

Diese Matrix erzählt uns eine faszinierende Geschichte über die Natur dieses Marktes. Wir sehen, dass alle drei Zustände eine erhebliche „Trägheit" aufweisen – die Wahrscheinlichkeit, im aktuellen Zustand zu verbleiben, ist deutlich höher als in einen anderen zu wechseln. Dies gilt insbesondere für das FLAT-Regime (Seitwärtsbewegung), bei dem die Wahrscheinlichkeit des Fortbestehens 0,68 beträgt, was die bekannte Tendenz des Marktes widerspiegelt, eine beträchtliche Zeit in Konsolidierungsphasen zu verbringen.

Training des neuronalen Netzes

Der nächste Schritt ist das Training des neuronalen Netzes, ein Prozess, der mit der Erziehung eines Finanzfachmanns vergleichbar ist. Wir sammeln sorgfältig historische Daten, strukturieren sie, extrahieren ihre Essenz in Form von Markov-Übergangsmatrizen und füttern dann unser digitales neuronales Netz mit diesem geistigen Nektar:

// Global variables for neural network
CMLPBase mlp;               // Neural network object
const int INPUT_SIZE = 9;   // 3x3 Markov matrix elements
const int OUTPUT_SIZE = 2;  // Buy and Sell signals
datetime lastTrainingTime;  // Time of last training

// Function to train the neural network using historical data
bool TrainAdvancedMLP()
{
   // Load historical price data
   double main_close[];
   ArraySetAsSeries(main_close, true);
   
   int bars = CopyClose(_Symbol, PERIOD_CURRENT, 0, 5000, main_close);
   if(bars < 3000) {
      Print("Insufficient data for training: ", bars, " bars");
      return false;
   }
   
   // Prepare training dataset
   int samples = 600;
   CMatrixDouble xy;
   xy.Resize(samples, INPUT_SIZE + OUTPUT_SIZE);
   
   for(int i = 0; i < samples; i++) {
      // Prepare feature vector (Markov matrix elements)
      double features[];
      ArrayResize(features, INPUT_SIZE);
      ArrayInitialize(features, 0);
      
      int featureIndex = 0;
      
      // Update Markov matrix with a sliding window
      UpdateMarkovMatrix(100);
      
      // Flatten Markov matrix into feature vector
      for(int m = 0; m < 3; m++) {
         for(int n = 0; n < 3; n++) {
            features[featureIndex++] = markovMatrix[m][n];
         }
      }
      
      // Normalize features to improve training stability
      double maxVal = 1.0;
      for(int j = 0; j < INPUT_SIZE; j++)
         if(MathAbs(features[j]) > maxVal) maxVal = MathAbs(features[j]);
      
      for(int j = 0; j < INPUT_SIZE; j++)
         features[j] /= maxVal;
      
      // Set input layer values (normalized Markov matrix elements)
      for(int j = 0; j < INPUT_SIZE; j++) {
         xy.Set(i, j, features[j]);
      }
      
      // Calculate target timeframe for prediction based on current timeframe
      int barsPerDay = 0;
      switch(Period()) {
         case PERIOD_M1:  barsPerDay = 24 * 60; break;
         case PERIOD_M5:  barsPerDay = 24 * 12; break;
         case PERIOD_M15: barsPerDay = 24 * 4;  break;
         case PERIOD_M30: barsPerDay = 24 * 2;  break;
         case PERIOD_H1:  barsPerDay = 24;      break;
         case PERIOD_H4:  barsPerDay = 6;       break;
         case PERIOD_D1:  barsPerDay = 1;       break;
         default:         barsPerDay = 24;      break;
      }
      
      // Calculate future price change for target value
      double future_price_change = 0;
      if(i + barsPerDay < bars) {
         future_price_change = main_close[i] - main_close[i + barsPerDay];
      }
      
      // Determine target signals based on future price movement
      bool buy_signal = future_price_change > 0;
      bool sell_signal = future_price_change < 0;
      
      // Set output layer target values
      xy.Set(i, INPUT_SIZE + 0, buy_signal ? 1.0 : 0.0);
      xy.Set(i, INPUT_SIZE + 1, sell_signal ? 1.0 : 0.0);
   }
   
   // Initialize neural network if not done already
   if(mlp.GetNeuronCount() == 0) {
      int network_structure[] = {INPUT_SIZE, 40, OUTPUT_SIZE};
      mlp.Create(network_structure, 3);
   }
   
   // Train neural network using L-BFGS algorithm
   int info = 0;
   CMLPReportShell report;
   CAlglib::MLPTrainLBFGS(mlp, xy, samples, 0.001, 5, 0.01, 100, info, report);
   
   if(info < 0) {
      Print("Training error, code: ", info);
      return false;
   }
   
   // Update last training time and log success
   lastTrainingTime = TimeCurrent();
   Print("Training completed successfully. Used ", samples, " examples of Markov matrix");
   return true;
}

// Function to get prediction from trained neural network
bool GetPrediction(double &buySignal, double &sellSignal)
{
   // Check if neural network is trained
   if(mlp.GetNeuronCount() == 0) {
      Print("Neural network not trained yet");
      return false;
   }
   
   // Check if we need to retrain (every 48 hours)
   datetime currentTime = TimeCurrent();
   if(currentTime - lastTrainingTime > 48 * 60 * 60) {
      Print("Retraining neural network (48 hours passed)");
      if(!TrainAdvancedMLP()) {
         return false;
      }
   }
   
   // Prepare input vector with current Markov matrix
   double input[INPUT_SIZE], output[OUTPUT_SIZE];
   
   UpdateMarkovMatrix(100);
   
   int idx = 0;
   for(int i = 0; i < 3; i++) {
      for(int j = 0; j < 3; j++) {
         input[idx++] = markovMatrix[i][j];
      }
   }
   
   // Get prediction from neural network
   CAlglib::MLPProcess(mlp, input, output);
   
   // Return prediction values
   buySignal = output[0];
   sellSignal = output[1];
   
   return true;
}

Dieser Code ist ein echtes alchemistisches Labor, in dem rohe Marktdaten in ein wertvolles Wissenselixier verwandelt werden. Das Training eines neuronalen Netzes kann in mehrere wichtige Phasen unterteilt werden:

  1. Vorbereitung der Daten: Wir bilden eine Trainingsmenge von 600 Beispielen, wobei die Eingabedaten die Elemente der Markov-Übergangsmatrix sind und die Zielwerte zukünftige Kursbewegungen über ein Zeitintervall in Abhängigkeit vom aktuellen Zeitrahmen darstellen.
  2. Normalisierung der Merkmale: Alle Elemente der Übergangsmatrix sind normalisiert, um Stabilität und Trainingseffizienz zu gewährleisten – eine klassische Technik des maschinellen Lernens, die die Dominanz einzelner Merkmale vermeidet und die Konvergenz des Algorithmus beschleunigt.
  3. Initialisierung und Training des Netzwerks: Wir verwenden eine dreischichtige Architektur (9 Eingangsneuronen, 40 versteckte Neuronen und 2 Ausgangsneuronen) und den L-BFGS-Algorithmus (Limited-memory Broyden-Fletcher-Goldfarb-Shanno), eine der effektivsten Optimierungsmethoden für das Training neuronaler Netze.
  4. Regelmäßiges Retraining: Das System wird automatisch alle 48 Stunden erneut trainiert, sodass es sich an veränderte Marktbedingungen anpassen kann.

Beachten Sie die elegante Art und Weise, wie sich das System an unterschiedliche Zeitrahmen anpasst: Die Variable barsPerDay wird automatisch angepasst, sodass das System künftige Kursänderungen konsistent vorhersagen kann, unabhängig davon, ob wir mit Minuten- oder Tagescharts arbeiten. Diese universelle Lösung macht den EA zu einem außergewöhnlich flexiblen Werkzeug, das ohne zusätzliche Konfiguration in jedem Zeitrahmen arbeiten kann.

Eines der Merkmale unserer Implementierung ist auch die Verwendung eines „gleitenden Fensters" für die Aktualisierung der Markov-Matrix. Für jedes Trainingsbeispiel wird die Übergangsmatrix auf der Grundlage der vorangegangenen 100 Bars neu berechnet, was es dem neuronalen Netz ermöglicht, die Beziehung zwischen lokalen Marktmerkmalen und nachfolgenden Kursbewegungen zu erfassen.

Die Funktion GetPrediction veranschaulicht, wie ein trainiertes neuronales Netz zur Generierung von Handelssignalen verwendet wird: Die aktuelle Markov-Übergangsmatrix wird in einen Merkmalsvektor umgewandelt, der in den Eingang des neuronalen Netzes eingespeist wird, und der Ausgang sind die Wahrscheinlichkeiten von Preissteigerungen und -senkungen. Diese Wahrscheinlichkeiten werden direkt für Handelsentscheidungen verwendet, wie wir im nächsten Abschnitt sehen werden.

Die Strategie der Handelsentscheidung und ihre Kunst des Kapitalschutzes

Es ist nun an der Zeit, einen Blick darauf zu werfen, wie das System Handelsentscheidungen trifft:

// Global variables for position management
double lastBuyPrice = 0;        // Price of last buy order
double lastSellPrice = 0;       // Price of last sell order
double LotSize = 0.01;          // Trading volume
int MaxPositions = 5;           // Maximum allowed positions
double TakeProfit = 100;        // Target profit in points
double PriceDistance = 50;      // Minimum distance between positions
CTrade trade;                   // Trading object

// Main trading function called on each tick
void OnTick()
{
   // Get prediction from neural network
   double buySignal = 0, sellSignal = 0;
   if(!GetPrediction(buySignal, sellSignal)) {
      return; // Exit if prediction fails
   }
   
   // Process closing of profitable positions first
   CheckProfitClosure();
   
   // Check if maximum positions limit is reached
   int totalPositions = CountOpenPositions();
   if(totalPositions >= MaxPositions) return;
   
   // Get current market prices
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick)) return;
   
   // Open BUY position if:
   // 1. Buy signal is strong enough (threshold 0.55)
   // 2. We haven't reached max positions for BUY
   // 3. Price is far enough from the last buy to avoid clustering
   if(buySignal > 0.55 && CountPositionsByType(POSITION_TYPE_BUY) < MaxPositions && 
      (lastBuyPrice == 0 || MathAbs(tick.ask - lastBuyPrice) > PriceDistance*_Point)) {
      if(trade.Buy(LotSize, _Symbol, tick.ask, 0, 0, "MLP_Buy")) {
         lastBuyPrice = tick.ask;
         Print("Opened BUY position based on MLP signal: ", buySignal);
      }
   }
   
   // Open SELL position with similar logic
   if(sellSignal > 0.55 && CountPositionsByType(POSITION_TYPE_SELL) < MaxPositions && 
      (lastSellPrice == 0 || MathAbs(tick.bid - lastSellPrice) > PriceDistance*_Point)) {
      if(trade.Sell(LotSize, _Symbol, tick.bid, 0, 0, "MLP_Sell")) {
         lastSellPrice = tick.bid;
         Print("Opened SELL position based on MLP signal: ", sellSignal);
      }
   }
}

// Function to check and close profitable positions
void CheckProfitClosure()
{
   int total = PositionsTotal();
   
   for(int i = total - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      // Skip positions of other symbols
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      
      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
      ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      
      // Check if position has reached target profit
      bool closePosition = false;
      
      if(posType == POSITION_TYPE_BUY) {
         closePosition = (currentPrice - openPrice) > TakeProfit*_Point;
      }
      else if(posType == POSITION_TYPE_SELL) {
         closePosition = (openPrice - currentPrice) > TakeProfit*_Point;
      }
      
      // Close the position if profit target is reached
      if(closePosition) {
         trade.PositionClose(ticket);
         Print("Closed position ", ticket, " with profit");
      }
   }
}

// Helper function to count all open positions for the current symbol
int CountOpenPositions()
{
   int count = 0;
   int total = PositionsTotal();
   
   for(int i = 0; i < total; i++) {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
         count++;
      }
   }
   
   return count;
}

// Helper function to count positions by type (BUY or SELL)
int CountPositionsByType(ENUM_POSITION_TYPE type)
{
   int count = 0;
   int total = PositionsTotal();
   
   for(int i = 0; i < total; i++) {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      if(PositionGetString(POSITION_SYMBOL) == _Symbol && 
         (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type) {
         count++;
      }
   }
   
   return count;
}

Das Hauptmerkmal unseres EA ist die Fähigkeit, gleichzeitig Long- und Short-Positionen zu halten und so Hedging-Paare zu bilden. Diese Strategie unterscheidet sich grundlegend von traditionellen Ansätzen, die eine klare Definition der Marktrichtung erfordern. Anstelle einer Dichotomie „Bulle oder Bär" erkennt unser System an, dass der Markt vielschichtig ist und sich verschiedene Aspekte gleichzeitig in unterschiedliche Richtungen bewegen können.

Das Hedging-Konzept in unserem System wird durch die gleichzeitige Eröffnung von Positionen in beide Richtungen umgesetzt, wenn das neuronale Netz hohe Wahrscheinlichkeiten für Aufwärts- und Abwärtsbewegungen anzeigt. Dies kann z. B. in Zeiten hoher Volatilität oder vor wichtigen wirtschaftlichen Ereignissen der Fall sein. Dieser Ansatz kann mit einer Schachpartie verglichen werden, bei der ein erfahrener Großmeister oft einen Angriff auf einer Flanke entwickelt und gleichzeitig die Verteidigung auf der anderen Seite verstärkt.

Hedging-Positionen wirken wie eine Versicherung füreinander – wenn sich der Markt für eine bestimmte Bewegungsrichtung entscheidet, wird eine der Positionen profitabel und die andere unprofitabel. Mit den richtigen Einstellungen (insbesondere TakeProfit) schließt das System jedoch schnell profitable Positionen und hält unrentable offen, um auf eine Marktumkehr zu warten. Diese Asymmetrie – schnelles Mitnehmen von Gewinnen und geduldiges Abwarten bei Verlustpositionen – liefert langfristig eine positive mathematische Erwartung für das System.

Erwähnenswert ist auch der elegante Mechanismus zur Positionsverwaltung: Der EA eröffnet nicht nur bei jedem Signal einen neuen Handel, sondern berücksichtigt auch bestehende Positionen und hält einen Mindestabstand zwischen den Einstiegspunkten ein (Parameter PriceDistance). Dies verhindert eine übermäßige Risikokonzentration und sorgt für eine gleichmäßigere Kapitalverteilung.

Von besonderem Interesse ist das Verhalten unseres Systems an den Grenzen von Marktregimen, wenn der Markt von einem Trendzustand in einen Seitwärtszustand oder umgekehrt wechselt. In solchen Momenten scheitern traditionelle Systeme oft, weil sie feststellen, dass ihre „Karte“ nicht mehr mit der „Landschaft“ übereinstimmt. Unser System passt sich durch die kontinuierliche Aktualisierung der Markov-Matrix und die regelmäßige Umschulung des neuronalen Netzes schnell an Veränderungen an, was es in Zeiten erhöhter Unsicherheit besonders effektiv macht.


Tests und Optimierung: Von der Theorie zur Praxis

Nach der Implementierung der grundlegenden EA-Struktur haben wir uns in eine faszinierende Studie über seine Effizienz anhand historischer Daten für mehrere Währungspaare im Zeitraum 2017-2025 gestürzt. Die Ergebnisse übertrafen die kühnsten Erwartungen, insbesondere bei den Währungspaaren mit hoher Liquidität – EURUSD und GBPUSD.

Sehen wir uns eine detaillierte Analyse der Testergebnisse für das Paar EURUSD mit den während der Optimierung festgelegten Parametern an (LotSize = 0,01, MaxPositions = 5, ATR_Period = 14)

Wir werden uns diese Metriken genauer ansehen:

  1. Durchschnittliche jährliche Rendite: 66,7%
    Dies liegt deutlich über dem Durchschnitt selbst aktiv verwalteter Investmentfonds, die in der Regel 10-15 % pro Jahr anstreben. Diese hohe Rendite zeigt, dass das System in der Lage ist, Marktchancen effektiv zu erkennen und zu nutzen.
  2. Maximaler Drawdown: 11%
    Dieser Indikator spiegelt den größten prozentualen Rückgang des Equity-Kapitals von einem Höchststand zu einem Minimum vor einem neuen Höchststand wider. Der relativ niedrige Drawdown für ein System mit einer derartigen Rendite deutet auf die Effizienz der Absicherungs- und Risikomanagementstrategie hin.
  3. Sharpe Ratio: 1,3
    Die Sharpe Ratio ist ein häufig verwendetes Maß für die Anlageperformance, das den Kompromiss zwischen Rendite und Risiko berücksichtigt. Ein Wert über 1,0 gilt als gut, ein Wert von 1,3 als exzellentes Ergebnis, das auf hohe Erträge im Verhältnis zum eingegangenen Risiko hinweist.
  4. Prozentsatz der profitablen Geschäfte: 44,7%
    Dieser Indikator, der auch als „Gewinnrate" bezeichnet wird, zeigt an, dass mehr als 4 von 10 Trades des EAs profitabel sind. Dies ist ein hoher Wert für ein algorithmisches System, insbesondere wenn man bedenkt, dass eine beträchtliche Anzahl von Trades abgeschlossen wurde (182.524).
  5. Profit Factor: 1,2
    Das Verhältnis von Gesamtgewinn zu Gesamtverlust. Ein Wert von 1,2 bedeutet, dass das System 20 % mehr Gewinn als Verlust erzielt, was ein klares Zeichen für seine Effizienz ist.
  6. Recovery Factor 7,64

So überzeugend die Zahlen auch sind, sie erfassen die wichtigste Leistung des EAs nicht vollständig: Er hat eine bemerkenswerte Stabilität über ein breites Spektrum von Marktzuständen hinweg gezeigt. Wie ein erfahrener Surfer glitt er meisterhaft über die Marktwellen, unabhängig von ihrer Höhe und Beschaffenheit.

Das Systemverhalten ist besonders in Zeiten starker Marktturbulenzen aufschlussreich. Während des starken Anstiegs des Dollars im März 2024, als viele traditionelle algorithmische Systeme erhebliche Verluste erlitten, hat unser EA nicht nur das Kapital bewahrt, sondern auch positive Renditen erzielt. Erreicht wurde dies durch ein rechtzeitiges Retraining des neuronalen Netzes, das sich an veränderte Marktbedingungen anpassen konnte, und eine effiziente Absicherung, die das Kapital vor einseitigen Preisbewegungen schützte.

Ein weiterer Vorteil des Systems ist seine Fähigkeit, in verschiedenen Marktregimen zu arbeiten. Während viele algorithmische Strategien entweder für steigende oder seitwärts tendierende Märkte optimiert sind, ist unser System dank eines adaptiven Mechanismus zur Zustandserkennung und einer flexiblen Absicherungsstrategie in beiden Bereichen erfolgreich.


Über das Grundmodell hinaus: Hinweise zur Erweiterung

Die vorgestellte Implementierung ist nur die erste Note in einer potentiellen Sinfonie von Möglichkeiten. Es gibt noch viele spannende Möglichkeiten, das System weiter zu verbessern.

Ausweitung des Zustandsmodells

Stellen Sie sich ein System vor, in dem sich anstelle des bescheidenen Dreiklangs von Marktzuständen (Aufwärtstrend, Flaute, Abwärtstrend) ein ganzes Spektrum von Marktstimmungen entfaltet: von einem rasanten Aufwärtsgalopp bis zu einem rasanten Abwärtsangriff, von einer kaum merklichen positiven Tendenz bis zu einem sanften Abwärtsrutschen, mit einer ausgeprägten Seitwärtsphase im Zentrum dieses Kontinuums.

// Enhanced market state enumeration
enum ENHANCED_MARKET_STATE
{
   STATE_STRONG_DOWNTREND = 0,    // Strong bearish movement
   STATE_MODERATE_DOWNTREND = 1,  // Moderate bearish movement
   STATE_WEAK_DOWNTREND = 2,      // Weak bearish movement
   STATE_FLAT = 3,                // Sideways market
   STATE_WEAK_UPTREND = 4,        // Weak bullish movement
   STATE_MODERATE_UPTREND = 5,    // Moderate bullish movement
   STATE_STRONG_UPTREND = 6       // Strong bullish movement
};

// Enhanced market state detection function
ENHANCED_MARKET_STATE GetEnhancedMarketState(int shift)
{
   double close[], atr[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(atr, true);
   
   // Get data
   if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 ||
      CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) {
      return STATE_FLAT;
   }
   
   // Calculate normalized price change
   double priceChange = close[0] - close[1];
   double atrValue = atr[0];
   double normalizedChange = priceChange / atrValue;
   
   // Determine enhanced market state based on price change relative to ATR
   if(normalizedChange < -1.5) return STATE_STRONG_DOWNTREND;
   if(normalizedChange < -0.75) return STATE_MODERATE_DOWNTREND;
   if(normalizedChange < -0.25) return STATE_WEAK_DOWNTREND;
   if(normalizedChange <= 0.25) return STATE_FLAT;
   if(normalizedChange <= 0.75) return STATE_WEAK_UPTREND;
   if(normalizedChange <= 1.5) return STATE_MODERATE_UPTREND;
   return STATE_STRONG_UPTREND;
}

Anreicherung des Marktkontextes

Das derzeitige Modell verwendet in erster Linie den ATR zur Bestimmung der Marktlage. Aber stellen Sie sich die Tiefe des Verständnisses vor, die wir erreichen können, wenn wir diesem Orchester den Klang des RSI, die Melodie des MACD, die harmonischen Sequenzen der Fibonacci-Levels und die rhythmischen Strukturen der Volumen hinzufügen!

So wie sich die menschlichen Sinne zu einer ganzheitlichen Wahrnehmung der Welt verbinden, kann auch eine Vielzahl von technischen Indikatoren unserem System ein nahezu intuitives Verständnis der Marktdynamik vermitteln. Die Kombination von Oszillatoren, Trend- und Volumenindikatoren kann zu einem qualitativen Sprung in der Prognosegenauigkeit führen.

Dynamische Anpassung der Positionsgrößen

Die Idee der dynamischen Anpassung der Positionsgrößen verdient besondere Aufmerksamkeit. Stellen Sie sich ein System vor, das wie ein erfahrener Kapitän die „Segelfläche" vergrößert oder verkleinert, je nachdem, wie stark der „Wind" auf dem Markt ist, wie viel Vertrauen in den gewählten Kurs besteht und wie viel Erfahrung mit der Navigation in ähnlichen Gewässern vorhanden ist.

In Zeiten hoher Sicherheit und günstiger Marktbedingungen erhöht der EA die Positionsgrößen und maximiert so die Rendite aus seinem Prognosevorteil. Umgekehrt reduziert das System in Zeiten erhöhter Turbulenzen oder widersprüchlicher Signale automatisch das Handelsvolumen und bewahrt so das Kapital für günstigere Gelegenheiten.

Analyse mehrerer Zeitrahmen

Schließlich wird die Analyse mehrerer Zeitrahmen eine neue Dimension des Marktverständnisses eröffnen. So wie ein Archäologe gleichzeitig die gesamte geologische Epoche und die kleinsten Details eines Artefakts untersucht, wird unser System in der Lage sein, gleichzeitig sowohl globale tektonische Verschiebungen auf dem Markt als auch die kleinsten Preisschwankungen zu erfassen.

Stellen Sie sich einen EA vor, der Markov-Ketten über mehrere Zeitrahmen hinweg gleichzeitig analysiert, vom Monat bis zur Minute, und sich ein integriertes Bild des Marktes macht, bei dem langfristige Trends den Rahmen für kurzfristige Schwankungen vorgeben. Mit diesem Ansatz können Sie nicht nur die Bewegungsrichtung genauer bestimmen, sondern auch ideale Einstiegspunkte mit Tick-Genauigkeit ermitteln.


Epilog: Der Stein der Weisen des algorithmischen Handels

Der in diesem Artikel vorgestellte EA ist nicht nur eine Symbiose aus verschiedenen technischen Ansätzen. Dies ist eine echte Alchemie der Finanztechnologien, bei der aus dem Zusammenspiel unterschiedlicher Elemente etwas qualitativ Neues entsteht – so wie in den Legenden der Stein der Weisen gewöhnliche Metalle in Gold verwandelt.

Durch die Kombination der Strenge der mathematischen Wahrscheinlichkeitstheorie mit der intuitiven Kraft der künstlichen Intelligenz und der pragmatischen Weisheit der Hedging-Strategien haben wir ein System geschaffen, das in der Lage ist, die turbulenten Gewässer der Finanzmärkte mit der Anmut eines erfahrenen Seemanns zu navigieren. Bei ruhigem Wetter fängt es mit seinen empfindlichen Segeln die kleinste Luftbewegung ein; bei Sturm manövriert es meisterhaft zwischen riesigen Wellen der Unbeständigkeit; und wenn der Wind dreht, passt es sofort seinen Kurs an.

Es ist wichtig zu verstehen: Wir haben kein magisches Artefakt geschaffen, das unbegrenzten Reichtum verspricht. Vielmehr handelt es sich um ein empfindliches Musikinstrument, das von seinem Besitzer das Stimmen auf bestimmte Leistungsbedingungen und sein Können verlangt. So wie eine Stradivari-Violine ihren göttlichen Klang nur in den Händen eines Virtuosen entfaltet, so entfaltet auch unser EA sein volles Potenzial nur mit der richtigen Stimmung und einem tiefen Verständnis seiner inneren Architektur.

Die adaptive Natur des Systems und die eleganten Risikomanagement-Mechanismen machen dieses Tool jedoch sowohl für Anfänger, die ihre ersten Schritte in die Welt des algorithmischen Handels machen, als auch für erfahrene Experten, die nach neuen Dimensionen in ihrem Handelsarsenal suchen, zugänglich.

Der EA-Quellcode, der dem genetischen Code eines neuartigen Handelssystems gleicht, ist im Anhang des Artikels verfügbar. Er ist offen für Experimente, Änderungen und Entwicklungen. Wir laden Sie nicht nur ein, sie zu nutzen, sondern auch Mitautoren des nächsten Kapitels in dieser spannenden Geschichte der Entwicklung der Finanztechnologie zu werden.

Denn echte Innovation entsteht aus einem offenen Ideendialog und dem ständigen Streben nach Spitzenleistungen.


Links und zusätzliche Materialien

  1. Koshtenko, Y. (2025). Markov Chain-Based Matrix Forecasting Model 
  2. ALGLIB - Numerical Analysis Library: https://www.alglib.net/
  3. MQL5-Dokumentation: https://www.mql5.com/de/docs
  4. Sewell, M. (2011). Characterization of Financial Time Series. UCL Research Note, 11(01).
  5. Zhang, G.P. (2003). Time series forecasting using a hybrid ARIMA and neural network model. Neurocomputing, 50, 159-175.

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

Beigefügte Dateien |
Matrix_MLP_EA.mq5 (35.48 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (10)
Victor Golovkov
Victor Golovkov | 22 Mai 2025 in 10:28

Bewusste oder unbewusste, aber eklatante Manipulation von Testergebnissen. (viele Autoren leiden darunter).

Der Expert Advisor wurde mit einem fixen Lot getestet, was jede Strategie völlig zunichte macht - da die Bedingungen für jeden nächsten Trade des Expert Advisors weniger riskant werden. Daher auch der niedrige Drawdown-Prozentsatz. Für ein solches Testerbild ist es nicht notwendig, Matrizen, KI usw. zu erstellen, es genügt, einen geeigneten Zeitpunkt für den Test zu finden.

Meines Erachtens sollte ein Expert Advisor (nicht nur dieser) mit einer Losgröße getestet werden, die durch die Einlagegröße (in Prozent) bestimmt wird. Dann wird jeder Handel im Test wie der erste sein. In der Tat werden die Bedingungen für jedes Geschäft in Bezug auf das Risiko immer die gleichen sein. Und hier wird das Bild völlig anders sein.

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 Mai 2025 in 15:44

Seltsam, aber die Datei aus der Box ist bereits mit einem Fehler kompiliert (DeInit )))). Es ist nicht klar, bei welchen Einstellungen es getestet wurde - von der gleichen "Box" gibt es kosmische Zahlen. Und wenn man das AI-Wasser entfernt, dann gibt es am Ende nichts zu lesen. Sie können etwas genauer sein.


Übrigens, füllen Sie den KI-Text"Das aktuelle Modell verwendet hauptsächlich ATR, um die Marktbedingungen zu bestimmen. Aber stellen Sie sich vor, welche Tiefe des Verständnisses wir erreichen werden, wenn wir diesem Orchester den Klang des RSI, die Melodie des MACD, die harmonischen Sequenzen der Fibonacci-Levels und die rhythmischen Strukturen der Volumina hinzufügen!". Er wird Ihnen eine solche Geschichte liefern!!!!)))))

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 Mai 2025 in 15:46
Hinzugefügt rsi, macd, fibo, Volumen, wenn jemand interessiert ist
Aleksey Vyazmikin
Aleksey Vyazmikin | 22 Mai 2025 in 16:07
sportoman #:
Hinzugefügt rsi, macd, fibo, Volumen, wenn jemand interessiert ist.

Auf dem Forum können nur Quellen gepostet werden, sonst können sie verbieten.

Eigentlich, was ist die Wirkung der Zusätze?

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 Mai 2025 in 17:05
Aleksey Vyazmikin #:

Im Forum können nur Quellen veröffentlicht werden, ansonsten können sie gesperrt werden.

Was bewirken eigentlich die Zusatzstoffe?

Daskomische ist, dass der Expert Advisor nicht auf dem Tester funktioniert. Ich verstehe nicht, was und wie der Autor es getestet hat. Ich habe es auf Demo für alle Paare, ich werde sehen, was und wie

Das Hilbert-Schmidt-Unabhängigkeitskriterium (HSIC) Das Hilbert-Schmidt-Unabhängigkeitskriterium (HSIC)
Der Artikel behandelt den nichtparametrischen statistischen Test HSIC (Hilbert-Schmidt Independence Criterion), mit dem sich lineare und nichtlineare Abhängigkeiten in Daten ermitteln lassen. Es werden zwei Implementierungen zur Berechnung von HSIC in der Sprache MQL5 vorgestellt: der exakte Permutationstest und die Gamma-Approximation. Die Leistungsfähigkeit der Methode wird an synthetischen Daten demonstriert, die eine nichtlineare Beziehung zwischen Merkmalen und der Zielvariablen modellieren.
Marktsimulation (Teil 19): Erste Schritte mit SQL (II) Marktsimulation (Teil 19): Erste Schritte mit SQL (II)
Wie wir im ersten Artikel über SQL erklärt haben, ist es sinnlos, Zeit in die Programmierung von Prozeduren zu investieren, um das zu tun, was bereits in SQL integriert ist. Ohne die Grundlagen zu kennen, werden Sie jedoch nicht in der Lage sein, irgendetwas mit SQL zu tun oder die Vorteile dieses Tools voll auszuschöpfen. In diesem Artikel werden wir uns daher ansehen, wie man grundlegende Aufgaben in Datenbanken durchführt.
Implementierung eines Break-Even-Mechanismus in MQL5 (Teil 1): Basisklasse und Break-Even-Modus auf Basis fester Punkte Implementierung eines Break-Even-Mechanismus in MQL5 (Teil 1): Basisklasse und Break-Even-Modus auf Basis fester Punkte
Dieser Artikel befasst sich mit der Anwendung eines Break-Even-Mechanismus in automatisierten Strategien, die die Sprache MQL5 verwenden. Wir beginnen mit einer einfachen Erklärung, was der Break-Even-Modus ist, wie er umgesetzt wird und welche Varianten möglich sind. Als Nächstes wird diese Funktionalität in den Expert Advisor Order Blocks integriert, den wir in unserem letzten Artikel über Risikomanagement erstellt haben. Um seine Wirksamkeit zu bewerten, werden wir zwei Backtests unter bestimmten Bedingungen durchführen: einen mit und einen ohne Break-Even-Mechanismus.
Von der Grundstufe bis zur Mittelstufe: Vererbung Von der Grundstufe bis zur Mittelstufe: Vererbung
Zweifellos wird dieser Artikel einen erheblichen Teil Ihrer Zeit in Anspruch nehmen, um zu verstehen, wie und warum das hier vorgestellte Material funktioniert. Denn alles, was hier gezeigt wird, orientiert sich zunächst an der objektorientierten Programmierung, basiert aber tatsächlich auf den Prinzipien der strukturierten Programmierung.