Entwicklung eines dynamischen Multi-Pair-EA (Teil 7): Paarübergreifende Korrelationsabbildung für die Handelsfilterung in Echtzeit
Inhaltsverzeichnis:
- Einführung
- Systemüberblick und Funktionsweise
- Die ersten Schritte
- Backtest-Ergebnisse
- Schlussfolgerung
Einführung
Sie starten einen EA für mehrere Symbole, und auf den ersten Blick scheint alles perfekt strukturiert zu sein: Jedes Symbol liefert klare Signale, die Einstiegspunkte sind präzise, und die Logik funktioniert genau wie vorgesehen. Sobald sich jedoch die Trades über mehrere Instrumente hinweg häufen, zeichnet sich ein beunruhigendes Muster ab. Was wie Diversifizierung aussah, entpuppt sich als Klumpenrisiko. Der EA eröffnet Positionen in Symbolen, deren Kurse sich in der Vergangenheit parallel entwickelt haben, wodurch das Risiko faktisch dupliziert wird. Wenn sich die Korrelationen verstärken – was bei Volatilitätsspitzen häufig der Fall ist –, beschleunigen sich die Drawdowns, nicht weil die einzelnen Signale falsch waren, sondern weil das Portfolio strukturell aus dem Gleichgewicht geraten ist. Es geht nicht mehr um die Genauigkeit der Signale, sondern um die Berücksichtigung von Wechselbeziehungen zwischen den Instrumenten.
Genau an dieser Stelle führt Teil 7 einen grundlegenden Perspektivwechsel ein. Anstatt jedes Symbol für sich zu bewerten, integrieren wir eine Echtzeit-Korrelationsmatrix über Symbolpaare hinweg direkt in die Entscheidungslogik. Bevor ein Trade ausgeführt wird, bewertet der EA nun, wie sich diese Position auf die bestehende Exposition im gesamten Handelsuniversum auswirkt. Wenn das neue Signal das korrelierte Risiko erhöht oder im Widerspruch zur übergeordneten Richtungsstruktur steht, kann es herausgefiltert werden. Während sich Teil 6 auf die Effizienz der Ausführung konzentrierte – also auf die Optimierung der Spread-Sensitivität und des Kostenrankings –, hebt das Framework auf ein portfolioweites, intelligentes Entscheidungsniveau. Der Schwerpunkt verlagert sich von der reinen Kosteneffizienz hin zu Richtungskonsistenz, Kohärenz der Expositionen und dynamischer Risikokontrolle im gesamten Multi-Pair-Ökosystem.
Systemüberblick und Funktionsweise
Unser EA-System ist als zentrale Portfolio-Engine konzipiert, die mehrere Symbole aus einem einzigen koordinierten Rahmen heraus überwacht, bewertet und handelt. Anstatt jedes Paar separat zu behandeln, verwaltet das System strukturierte symbolbezogene Daten, synchronisierte Kursdaten und eine gemeinsame Entscheidungspipeline, die Signale, Ausführungsfilter und Risikoparameter gemeinsam verarbeitet. Diese Architektur ermöglicht eine einheitliche Anwendung der Logik auf alle Instrumente und bietet gleichzeitig Flexibilität bei instrumentspezifischen Kennzahlen wie Volatilität, Spread-Bedingungen und Signalstärke.

Im Kern integriert der EA eine Echtzeit-Korrelationsanalyse über verschiedene Symbole hinweg, um die Handelsfilterung und die Portfolioausgewogenheit zu verbessern. Vor der Eröffnung neuer Positionen wertet das System statistische Zusammenhänge zwischen den Symbolen aus, um redundante Expositionen zu vermeiden und das kumulierte Richtungsrisiko zu verringern. Durch die Kombination aus strukturierter Symbolverwaltung, matrixbasierter Korrelationsindizierung und dynamischer Filterung innerhalb des OnTick-Zyklus fungiert der EA nicht nur als Signalgenerator, sondern als portfoliobewusste Entscheidungsmaschine, die sich kontinuierlich an die sich verändernden Beziehungen zwischen den Märkten anpasst.
Die ersten Schritte
//+------------------------------------------------------------------+ //| Cross-Pair Correlation.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #include <Trade\Trade.mqh> #include <Trade\PositionInfo.mqh> CTrade trade; CPositionInfo posInfo; //--- Input parameters input string Symbols = "XAUUSD,GBPUSD,USDJPY,USDZAR"; // Symbols to monitor (comma separated) input ENUM_TIMEFRAMES CorrTimeframe = PERIOD_M15; // Timeframe for correlation input int CorrelationPeriod = 100; // Bars for correlation calculation input double CorrelationThreshold = 0.7; // Strong correlation threshold input double MaxCorrelationExposure = 1.5; // Max net correlated exposure //--- Signal parameters (simple MA crossover) input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M15; // Timeframe for signal input int BiasLookbackBars = 20; input double BiasThreshold = 0.001; // 0.1% input int FastMAPeriod = 10; // Fast MA period input int SlowMAPeriod = 30; // Slow MA period //--- Trade execution input double LotSize = 0.1; // Fixed lot size input int StopLoss_Pips = 30; // Stop Loss in pips input int TakeProfit_Pips = 60; // Take Profit in pips input int MagicNumber = 123456; // EA magic number input int Slippage = 30; // Slippage in points //--- Optional features input bool UseHeatmap = false; // Draw correlation heatmap (optional)
Zu Beginn legen wir die grundlegende Struktur und Konfiguration des EA-Systems fest, indem wir die erforderlichen Handelsklassen (CTrade für die Ausführung und CPositionInfo für die Positionsverfolgung) importieren und alle zentralen Eingabeparameter definieren, die das Systemverhalten steuern. Zunächst legen wir die Liste der zu überwachenden Symbole, den Zeitrahmen und den Rückblickzeitraum für die Berechnung der symbolübergreifenden Korrelationen sowie die Schwellenwerte fest, anhand derer bestimmt wird, wann Korrelationen als stark gelten und wann die korrelierte Exposition insgesamt zu hoch wird. Dadurch wird sichergestellt, dass der EA von Anfang an unter Berücksichtigung des Portfolios arbeitet.
Anschließend definieren wir die Logik zur Signalerzeugung anhand eines auf einem gleitenden Durchschnitt basierenden Crossover-Modells, das durch einen Richtungsfilter ergänzt wird, sowie die vollständigen Einstellungen für die Handelsausführung, darunter Lotgröße, Stop-Loss, Take-Profit, Slippage und die MagicNumber zur Positionsidentifizierung. Außerdem bieten wir eine Option zur Steuerung der Visualisierung der Korrelations-Heatmap an.
//+------------------------------------------------------------------+ //| Symbol Data Structure | //+------------------------------------------------------------------+ struct SymbolData { string name; double close[]; }; //+------------------------------------------------------------------+ //| Global vars | //+------------------------------------------------------------------+ int m_numSymbols; SymbolData m_symbols[]; double m_correlationMatrix[]; datetime m_lastBarTime; bool m_correlationValid; int m_fastMA[]; int m_slowMA[]; datetime m_lastSignalBar[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ensure current chart symbol is included string list = Symbols; if(StringFind(list,_Symbol) < 0) list = list + "," + _Symbol; //--- Split symbol list string parts[]; int num = StringSplit(list,',',parts); if(num <= 0) { Print("No symbols specified!"); return(INIT_FAILED); } m_numSymbols = num; //--- Resize containers ArrayResize(m_symbols,m_numSymbols); for(int i=0; i<m_numSymbols; i++) { string sym = parts[i]; StringTrimRight(sym); if(!SymbolSelect(sym,true)) { Print("Failed to select symbol: ",sym); return(INIT_FAILED); } m_symbols[i].name = sym; //--- Resize close buffer ArrayResize(m_symbols[i].close,CorrelationPeriod); ArraySetAsSeries(m_symbols[i].close,true); } ArrayResize(m_correlationMatrix,m_numSymbols*m_numSymbols); ArrayResize(m_fastMA,m_numSymbols); ArrayResize(m_slowMA,m_numSymbols); ArrayResize(m_lastSignalBar,m_numSymbols); //--- Create MA handles for(int i=0; i<m_numSymbols; i++) { m_fastMA[i] = iMA(m_symbols[i].name, SignalTimeframe, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE); m_slowMA[i] = iMA(m_symbols[i].name, SignalTimeframe, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE); if(m_fastMA[i]==INVALID_HANDLE || m_slowMA[i]==INVALID_HANDLE) { Print("Failed to create MA handle for ",m_symbols[i].name); return(INIT_FAILED); } m_lastSignalBar[i] = 0; } m_lastBarTime = 0; m_correlationValid = false; EventSetTimer(60); trade.SetExpertMagicNumber(MagicNumber); Print("Multi-Pair Correlation EA initialized with ",m_numSymbols," symbols."); return(INIT_SUCCEEDED); }
In diesem Abschnitt wird ein strukturiertes Datenmodell für die EA mithilfe der Struktur SymbolData definiert, die den Namen jedes Symbols und den zugehörigen Schlusskurs-Puffer, der für Korrelationsberechnungen verwendet wird, kapselt. Indem symbolbezogene Daten in einer Struktur organisiert und mehrere Instanzen im Array m_symbols[] gespeichert werden, nutzt der EA ein skalierbares, objektähnliches Design, das die symbolbezogenen Daten klar von der globalen Systemlogik trennt. Diese Architektur wird durch mehrere globale Arrays und Zustandsvariablen unterstützt, darunter eine abgeflachte eindimensionale Korrelationsmatrix, MA-Handles für die Signalerzeugung, symbolbezogene Zeitsteuerungen für Signale sowie Status-Flags, die verfolgen, ob die Korrelationsdaten aktuell und gültig sind.
Innerhalb von OnInit() bereitet der EA seine Multi-Symbol-Umgebung dynamisch vor. Zunächst wird sichergestellt, dass das aktuelle Symbol des Charts in der Überwachungsliste enthalten ist. Anschließend wird die durch Kommas getrennte Eingabezeichenfolge in einzelne Symbole zerlegt. Jedes Symbol wird über SymbolSelect() validiert und ausgewählt; anschließend wird die Größe seines internen Schlusskurs-Puffers angepasst und dieser als Zeitreihen-Array für eine korrekte Indizierung konfiguriert. Gleichzeitig passt der EA die Größe der abgeflachten Korrelationsmatrix so an, dass sie alle Symbolpaar-Kombinationen (n × n) abdeckt, und bereitet Arrays vor, in denen später Indikator-Handles und Signalzeitdaten gespeichert werden. Durch diese dynamische Größenanpassung kann sich der EA automatisch an die vom Benutzer angegebene Anzahl von Symbolen anpassen.
Im letzten Schritt der Initialisierung werden die Handles für den schnellen und den langsamen gleitenden Durchschnitt (MA) für jedes Symbol erstellt, wodurch eine unabhängige Signalerzeugung im gesamten Portfolio ermöglicht wird. Außerdem initialisiert es Zustandssteuerungsvariablen wie m_lastBarTime und m_correlationValid, richtet einen Timer ein, um die periodische Neuberechnung der Korrelation auszulösen, und weist die MagicNumber des Experten zur Identifikation von Positionen/Orders zu.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release MA handles for(int i=0; i<m_numSymbols; i++) { if(m_fastMA[i]!=INVALID_HANDLE) IndicatorRelease(m_fastMA[i]); if(m_slowMA[i]!=INVALID_HANDLE) IndicatorRelease(m_slowMA[i]); } EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(IsNewBar()) { UpdateCorrelationMatrix(); m_correlationValid = true; } for(int i=0; i<m_numSymbols; i++) { string sym = m_symbols[i].name; datetime currBar = iTime(sym,SignalTimeframe,0); if(currBar != m_lastSignalBar[i]) { int signal = CheckSignal(i); if(signal != 0) { if(IsTradeAllowed(i,signal)) ExecuteTrade(i,signal); } m_lastSignalBar[i] = currBar; } } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(!m_correlationValid) UpdateCorrelationMatrix(); }
Die Funktion OnDeinit() sorgt für die ordnungsgemäße Freigabe von Ressourcen, wenn der EA entfernt oder die Plattform heruntergefahren wird. Das Programm durchläuft alle überwachten Symbole und gibt die Handles des Indikators des gleitenden Durchschnitts mithilfe von IndicatorRelease() sicher frei, wodurch Speicherlecks verhindert und Terminalressourcen freigegeben werden. Außerdem wird der Timer mit EventKillTimer() gestoppt, wodurch sichergestellt wird, dass nach der Deaktivierung des EA keine Hintergrundereignisse weiterlaufen. Dieser strukturierte Herunterfahrvorgang sorgt für einen effizienten Betrieb des Systems und verhindert, dass Indikator- oder Timer-Prozesse zurückbleiben, die die Leistung beeinträchtigen könnten.
Die Funktion OnTick() ist die zentrale Ausführungsengine des EAs. Zunächst wird geprüft, ob sich eine neue Referenzbar gebildet hat, und falls ja, wird die symbolübergreifende Korrelationsmatrix aktualisiert, um den Überblick über das Portfolio in Echtzeit aufrechtzuerhalten. Anschließend erkennt das System für jedes Symbol die Bildung einer neuen Signalbar, um eine wiederholte Verarbeitung innerhalb derselben Kerze zu vermeiden. Wenn eine neue Bar erkannt wird, wertet der EA Handelssignale aus, prüft anhand von Korrelations- und Expositionsfiltern, ob der Handel zulässig ist, und führt den Handel aus, sofern die Bedingungen erfüllt sind. Die Funktion OnTimer() dient als Sicherheitsmechanismus, der sicherstellt, dass die Korrelationsmatrix aktualisiert wird, falls sie noch nicht aktualisiert wurde, und so die Zuverlässigkeit des Systems sowie die Synchronisation zwischen den Symbolen gewährleistet.
//+------------------------------------------------------------------+ //| Helper function to get index in flattened matrix | //+------------------------------------------------------------------+ int CorrIndex(int i,int j) { return(i*m_numSymbols + j); } //+------------------------------------------------------------------+ //| Update close prices for all symbols | //+------------------------------------------------------------------+ void UpdateClosePrices() { for(int i=0; i<m_numSymbols; i++) { if(CopyClose(m_symbols[i].name, CorrTimeframe, 0, CorrelationPeriod, m_symbols[i].close) <= 0) { Print("Failed to copy close for ",m_symbols[i].name); } } } //+------------------------------------------------------------------+ //| Calculate Pearson correlation between two arrays | //+------------------------------------------------------------------+ double CalculateCorrelation(double &x[],double &y[],int length) { double sumX=0,sumY=0,sumXY=0,sumX2=0,sumY2=0; for(int i=0; i<length; i++) { sumX += x[i]; sumY += y[i]; sumXY += x[i]*y[i]; sumX2 += x[i]*x[i]; sumY2 += y[i]*y[i]; } double numerator = length*sumXY - sumX*sumY; double denominator = MathSqrt((length*sumX2 - sumX*sumX) * (length*sumY2 - sumY*sumY)); if(denominator == 0) return(0); return(numerator/denominator); } //+------------------------------------------------------------------+ //| Update the entire correlation matrix | //+------------------------------------------------------------------+ void UpdateCorrelationMatrix() { UpdateClosePrices(); for(int i = 0; i < m_numSymbols; i++) { for(int j = 0; j < m_numSymbols; j++) { if(i == j) { m_correlationMatrix[CorrIndex(i,j)] = 1.0; continue; } double corr = CalculateCorrelation( m_symbols[i].close, m_symbols[j].close, CorrelationPeriod); m_correlationMatrix[CorrIndex(i,j)] = corr; } } //--- Optional heatmap drawing if(UseHeatmap) DrawHeatmap(); } //+------------------------------------------------------------------+ //| Get symbol index by name | //+------------------------------------------------------------------+ int GetSymbolIndex(string sym) { for(int i = 0; i < m_numSymbols; i++) { if(m_symbols[i].name == sym) return(i); } return(-1); }
Dieser Abschnitt bildet das mathematische und strukturelle Fundament der Cross-Pair-Korrelations-Engine der EA. Die Hilfsfunktion CorrIndex() wandelt zweidimensionale Matrixkoordinaten (i, j) in einen eindimensionalen Array-Index um, wodurch die Korrelationsmatrix effizient als abgeflachtes Array gespeichert werden kann. Die Funktion UpdateClosePrices() ruft anschließend mithilfe von CopyClose() die aktuellen Schlusskurse für jedes überwachte Symbol ab und stellt so sicher, dass alle Korrelationsberechnungen auf synchronisierten und aktuellen Marktdaten basieren. Die Funktion CalculateCorrelation() implementiert die Pearson-Korrelationsformel manuell und berechnet den statistischen Zusammenhang zwischen zwei Kursreihen, indem sie Summen, Kreuzprodukte und quadrierte Werte aggregiert, bevor sie die normalisierte Korrelationsgleichung anwendet.

Die Funktion UpdateCorrelationMatrix() bindet alle Komponenten zusammen, indem sie zunächst die Kursdaten aktualisiert und anschließend alle Symbolpaare durchläuft, um deren Korrelationen zu berechnen und in der abgeflachten Matrix zu speichern. Die Diagonalelemente werden auf 1,0 gesetzt, um eine perfekte Selbstkorrelation darzustellen, während die Elemente außerhalb der Diagonalen dynamisch mithilfe der Pearson-Funktion berechnet werden. Wenn diese Option aktiviert ist, wird die Heatmap-Visualisierung unmittelbar nach der Neuberechnung aktualisiert, sodass die grafische Darstellung stets mit den aktuellsten Statistiken übereinstimmt. Die Funktion GetSymbolIndex() bietet eine einfache Suchfunktion, die einen Symbolnamen seiner internen Array-Position zuordnet und so eine effiziente Interaktion zwischen Handelslogik, Risikokontrolle und Korrelationsmatrix ermöglicht.
//+------------------------------------------------------------------+ //| Get bias (bullish/bearish) for a symbol based on price change | //+------------------------------------------------------------------+ int GetBias(int idx) { //--- Safety checks if(idx < 0 || idx >= m_numSymbols) return(0); if(BiasLookbackBars >= CorrelationPeriod) return(0); if(m_symbols[idx].close[BiasLookbackBars] == 0.0) return(0); double curr = m_symbols[idx].close[0]; double prev = m_symbols[idx].close[BiasLookbackBars]; if(prev == 0.0) return(0); double change = (curr - prev) / prev; if(change > BiasThreshold) return(1); // Bullish if(change < -BiasThreshold) return(-1); // Bearish return(0); } //+------------------------------------------------------------------+ //| Check if a trade is allowed based on correlation rules | //+------------------------------------------------------------------+ bool IsTradeAllowed(int index, int signal) { string sym = m_symbols[index].name; //--- Already in position? if(PositionSelect(sym)) return(false); if(!m_correlationValid) return(false); //--- Convert signal to direction ENUM_ORDER_TYPE direction; if(signal == 1) direction = ORDER_TYPE_BUY; else if(signal == -1) direction = ORDER_TYPE_SELL; else return(false); return(PassesCorrelationFilter(index, direction)); } //+------------------------------------------------------------------+ //| Generate trading signal (MA crossover) | //+------------------------------------------------------------------+ int CheckSignal(int index) { double fast[], slow[]; ArrayResize(fast, 2); ArrayResize(slow, 2); ArraySetAsSeries(fast, true); ArraySetAsSeries(slow, true); if(CopyBuffer(m_fastMA[index], 0, 0, 2, fast) < 2) return 0; if(CopyBuffer(m_slowMA[index], 0, 0, 2, slow) < 2) return 0; //--- Buy crossover: fast MA crosses above slow MA if(fast[1] <= slow[1] && fast[0] > slow[0]) return(1); //--- Sell crossover: fast MA crosses below slow MA if(fast[1] >= slow[1] && fast[0] < slow[0]) return(-1); return(0); }
In diesem Abschnitt stellen wir die Richtungsfilterung und die Logik zur Handelsvalidierung innerhalb des EAs vor. Die Funktion GetBias() ermittelt, ob ein Symbol eine bullische oder bärische Tendenz aufweist, indem sie die prozentuale Kursänderung über einen festgelegten Rückblickzeitraum misst. Sie umfasst Sicherheitsprüfungen, um ungültige Indizierungen oder Divisionsfehler zu verhindern, und vergleicht anschließend die berechnete Änderung mit einem konfigurierbaren Schwellenwert. Übersteigt der Kursanstieg den Schwellenwert, gibt die Funktion ein bullisches Signal (1) zurück; unterschreitet der Kursrückgang den Schwellenwert, gibt sie ein bärisches Signal (-1) zurück, andernfalls bleibt sie neutral (0). Dieser Bias-Mechanismus hilft dem EA dabei, über kurzfristige Crossover-Signale hinaus einen umfassenderen Richtungszusammenhang zu berücksichtigen.
Die Funktion IsTradeAllowed() fungiert als Schutzmechanismus, bevor eine Ausführung stattfindet. Sie blockiert Trades, wenn bereits eine Position in diesem Symbol besteht, wenn die Korrelationsmatrix veraltet ist oder wenn das Signal ungültig ist. Sobald die grundlegende Validierung erfolgreich abgeschlossen ist, wandelt das System das Signal in eine Orderrichtung um und leitet die Entscheidung an den Korrelationsfilter weiter, um die Exposition auf Portfolioebene zu steuern. Unterdessen generiert CheckSignal() Einstiegssignale mithilfe eines klassischen Moving-Average-Crossover-Ansatzes, bei dem die aktuellsten und die vorherigen Werte des schnellen und des langsamen gleitenden Durchschnitts verglichen werden. Zusammen sorgen diese Funktionen dafür, dass Trades nicht nur technisch ausgelöst, sondern auch kontextbezogen sowohl im Richtungskontext als auch auf Portfolioebene validiert werden.
//+------------------------------------------------------------------+ //| Calculate pip size for different instruments | //+------------------------------------------------------------------+ double CalculatePipSize(string symbol) { int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); //--- Detect pip size automatically if(StringFind(symbol, "JPY") != -1) // JPY pairs return((digits == 3) ? point * 10 : point); else if(StringFind(symbol, "XAU") != -1 || StringFind(symbol, "GOLD") != -1) // Metals return(0.10); else if(StringFind(symbol, "BTC") != -1 || StringFind(symbol, "ETH") != -1) // Cryptos return(point * 100.0); else if(StringFind(symbol, "US") != -1 && digits <= 2) // Indices (e.g. US30) return(point); else return((digits == 3 || digits == 5) ? point * 10 : point); // Default Forex } //+------------------------------------------------------------------+ //| Execute a trade (market order) with SL/TP | //+------------------------------------------------------------------+ void ExecuteTrade(int index, int signal) { string sym = m_symbols[index].name; double pipSize = CalculatePipSize(sym); double price, slPrice, tpPrice; if(signal == 1) // BUY { price = SymbolInfoDouble(sym, SYMBOL_ASK); slPrice = price - StopLoss_Pips * pipSize; tpPrice = price + TakeProfit_Pips * pipSize; if(trade.Buy(LotSize, sym, price, slPrice, tpPrice, "Correlation Filter EA")) Print("BUY executed on ", sym, " SL: ", DoubleToString(slPrice, _Digits), " TP: ", DoubleToString(tpPrice, _Digits)); else Print("BUY failed on ", sym, " Error: ", GetLastError()); } else if(signal == -1) // SELL { price = SymbolInfoDouble(sym, SYMBOL_BID); slPrice = price + StopLoss_Pips * pipSize; tpPrice = price - TakeProfit_Pips * pipSize; if(trade.Sell(LotSize, sym, price, slPrice, tpPrice, "Correlation Filter EA")) Print("SELL executed on ", sym, " SL: ", DoubleToString(slPrice, _Digits), " TP: ", DoubleToString(tpPrice, _Digits)); else Print("SELL failed on ", sym, " Error: ", GetLastError()); } } //+------------------------------------------------------------------+ //| Correlation filter logic | //+------------------------------------------------------------------+ bool PassesCorrelationFilter(int symbolIndex, ENUM_ORDER_TYPE direction) { for(int j = 0; j < m_numSymbols; j++) { if(j == symbolIndex) continue; double corr = m_correlationMatrix[CorrIndex(symbolIndex, j)]; if(MathAbs(corr) >= CorrelationThreshold) { //--- Check if correlated symbol already has open position if(PositionSelect(m_symbols[j].name)) { ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Same direction stacking if(corr > 0) { if((direction == ORDER_TYPE_BUY && posType == POSITION_TYPE_BUY) || (direction == ORDER_TYPE_SELL && posType == POSITION_TYPE_SELL)) { Print("Blocked by positive correlation with ", m_symbols[j].name); return(false); } } //--- Inverse stacking if(corr < 0) { if((direction == ORDER_TYPE_BUY && posType == POSITION_TYPE_SELL) || (direction == ORDER_TYPE_SELL && posType == POSITION_TYPE_BUY)) { Print("Blocked by inverse correlation with ", m_symbols[j].name); return(false); } } } } } return(true); }
Dieser Codeabschnitt ist für die Handelsausführung, die Risikodimensionierung und die portfoliogerechte Filterung zuständig und bildet den Kern der Echtzeit-Entscheidungsengine des EAs. Die Funktion CalculatePipSize() ermittelt den korrekten Pip-Wert für jeden Instrumententyp und berücksichtigt dabei die Unterschiede zwischen Devisenpaaren, JPY-Crosses, Metallen, Kryptowährungen und Indizes. Durch die Anpassung der Pip-Berechnungen auf der Grundlage von Symbolkonventionen stellt der EA sicher, dass Stop-Loss- und Take-Profit-Niveaus präzise angewendet werden, wodurch eine einheitliche Risikokontrolle über verschiedene Märkte hinweg gewährleistet wird.
Die Funktion ExecuteTrade() führt Marktorders mit dynamisch berechneten Stop-Loss- und Take-Profit-Niveaus aus, wobei die Pip-Größe aus CalculatePipSize() verwendet wird. Sie unterscheidet zwischen KAUF- und VERKAUF-Signalen, berechnet die entsprechenden Einstiegs-, SL- und TP-Kurse und führt den Handel mithilfe der CTrade-Klasse aus. Jede Ausführung wird mit detaillierten Informationen protokolliert, darunter auch, ob der Auftrag erfolgreich war oder fehlgeschlagen ist, und die berechneten SL/TP-Werte werden aus Gründen der Transparenz angezeigt, wodurch sowohl die operative als auch die Risikotransparenz für den Händler gewährleistet wird.
Die Funktion PassesCorrelationFilter() integriert das Risikomanagement auf Portfolioebene, indem sie prüft, ob ein neuer Handel gegen die Regeln zum Korrelationsrisiko verstoßen würde. Für jedes Symbol vergleicht sie das aktuelle Signal anhand der vorab berechneten Korrelationsmatrix mit allen anderen offenen Positionen. Trades werden blockiert, wenn bei stark korrelierten Symbolen bereits Positionen in derselben oder entgegengesetzter Richtung bestehen, je nach Vorzeichen der Korrelation. Dieser Mechanismus verhindert redundante oder sich verstärkende Risiken und stellt sicher, dass Trades nur dann ausgeführt werden, wenn sie ein ausgewogenes Risiko im gesamten Portfolio mit mehreren Symbolen gewährleisten, wodurch einzelne Signale effektiv mit der Gesamtsicherheit des Portfolios in Einklang gebracht werden.
//+------------------------------------------------------------------+ //| Convert symbol name to short display format | //+------------------------------------------------------------------+ string GetDisplayName(string symbol) { //--- If symbol contains "USD", replace it with "$" //--- Examples: XAUUSD -> XAU$, GBPUSD -> GBP$, USDJPY -> $JPY, USDZAR -> $ZAR int pos = StringFind(symbol, "USD"); if(pos >= 0) { if(pos == 0) // USD at beginning return("$" + StringSubstr(symbol, 3)); else // USD at end return(StringSubstr(symbol, 0, pos) + "$"); } return(symbol); // fallback } //+------------------------------------------------------------------+ //| Draw a correlation heatmap with row and column labels | //+------------------------------------------------------------------+ void DrawHeatmap() { ObjectsDeleteAll(0, "CorrHeatmap_"); string objName = "CorrHeatmap_"; int cellSize = 45; // Slightly larger for clean spacing int startX = 90; // More left margin for row labels int startY = 60; int half = cellSize / 2; //============================== // COLUMN LABELS (TOP) //============================== for(int j = 0; j < m_numSymbols; j++) { string label = objName + "col_" + IntegerToString(j); string dispName = GetDisplayName(m_symbols[j].name); ObjectCreate(0, label, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, label, OBJPROP_XDISTANCE, startX + j * cellSize + half); ObjectSetInteger(0, label, OBJPROP_YDISTANCE, startY - half); ObjectSetInteger(0, label, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetString(0, label, OBJPROP_TEXT, dispName); ObjectSetInteger(0, label, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 10); } //============================== // ROW LABELS (LEFT) //============================== for(int i = 0; i < m_numSymbols; i++) { string label = objName + "row_" + IntegerToString(i); string dispName = GetDisplayName(m_symbols[i].name); ObjectCreate(0, label, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, label, OBJPROP_XDISTANCE, startX - half); ObjectSetInteger(0, label, OBJPROP_YDISTANCE, startY + i * cellSize + half); ObjectSetInteger(0, label, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetString(0, label, OBJPROP_TEXT, dispName); ObjectSetInteger(0, label, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 10); } //============================== // CORRELATION MATRIX //============================== for(int i = 0; i < m_numSymbols; i++) { for(int j = 0; j < m_numSymbols; j++) { string label = objName + IntegerToString(i) + "_" + IntegerToString(j); double corr = m_correlationMatrix[CorrIndex(i, j)]; string text = DoubleToString(corr, 2); ObjectCreate(0, label, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, label, OBJPROP_XDISTANCE, startX + j * cellSize + half); ObjectSetInteger(0, label, OBJPROP_YDISTANCE, startY + i * cellSize + half); ObjectSetInteger(0, label, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetString(0, label, OBJPROP_TEXT, text); ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 10); //--- Color coding if(corr > 0.7) ObjectSetInteger(0, label, OBJPROP_COLOR, clrLime); else if(corr < -0.7) ObjectSetInteger(0, label, OBJPROP_COLOR, clrRed); else ObjectSetInteger(0, label, OBJPROP_COLOR, clrBlack); } } } //+------------------------------------------------------------------+
Hier legen wir den Schwerpunkt darauf, die Korrelationen zwischen den Paaren mithilfe einer Heatmap-Oberfläche klar und visuell intuitiv darzustellen. Die Funktion GetDisplayName() vereinfacht die Symbolnamen, indem sie USD aus Gründen der Lesbarkeit durch das $-Zeichen ersetzt und so prägnante Bezeichnungen wie XAU$ oder $JPY erzeugt. Diese Anzeigenamen werden in der Heatmap sowohl als Zeilen- als auch als Spaltenüberschriften verwendet, sodass sich die Symbole auf einen Blick leicht identifizieren lassen. Die Beschriftungen werden sorgfältig positioniert und relativ zu den Matrixzellen zentriert, um sicherzustellen, dass sie mit den angezeigten Korrelationswerten übereinstimmen.
Die Funktion DrawHeatmap() erstellt eine vollständige Matrixvisualisierung, indem sie alle Symbolpaare durchläuft. Für jede Zelle wird der Korrelationswert als Text dargestellt; die Farbgebung des Textes signalisiert positive, negative oder neutrale Korrelationen: Grün steht für positive Korrelationen, Rot für negative und Hellgrau für neutrale. Über jeder farbigen Zelle wird der tatsächliche numerische Korrelationswert angezeigt, wobei die Textfarbe je nach Korrelationswert kontrastoptimiert angepasst wird. Diese doppelte Darstellung von Farbe und Zahlenwert ermöglicht es Händlern, Zusammenhänge zwischen Symbolen schnell zu erkennen und gleichzeitig präzise, gut lesbare Informationen zu erhalten, wodurch rohe statistische Daten effektiv in ein praxisorientiertes visuelles Werkzeug umgewandelt werden.

Backtest-Ergebnisse
Die Tests wurden über einen Zeitraum von etwa zwei Monaten vom 1. Dezember 2025 bis zum 30. Januar 2026 unter den folgenden Bedingungen durchgeführt:

Nachfolgend sehen Sie die Equity-Kurve und die Backtest-Ergebnisse:


Schlussfolgerung
Wir sind weit über einen gewöhnlichen Signalgenerator für mehrere Paare hinausgegangen und haben das System zu einer echten Entscheidungsmaschine auf Portfolioebene weiterentwickelt. Die Symbol-Daten werden über alle Instrumente hinweg synchronisiert, Kurspuffer werden strukturiert verwaltet, und eine Pearson-Korrelationsmatrix in Echtzeit wird kontinuierlich neu erstellt und direkt in die abschließende Ausführungsschicht eingespeist. Die entscheidende Entwicklung ist nicht nur die Berechnung – es ist die Integration. Die Korrelation ist nicht mehr nur ein von außen beobachtetes Phänomen, sondern spielt nun eine aktive Rolle bei der letzten Überprüfung, bevor eine Order den Markt erreicht.
Das Ergebnis ist ein skalierbares EA-Framework, in dem Instrumente nahtlos hinzugefügt werden können, Korrelationen bei jeder neuen Bar (oder Timer-Ereignis) neu berechnet werden, um aktuelle Marktstatistiken widerzuspiegeln, und jedes MA-Crossover-Signal einen Portfolio-Expositions-Filter durchlaufen muss. Trades werden blockiert, wenn eine starke positive Korrelation das bestehende Richtungsrisiko verstärken würde, sowie wenn eine starke negative Korrelation ein verstecktes Spiegelrisiko gegenüber einer bereits offenen Position schaffen würde. Die Heatmap bietet eine unmittelbare visuelle Bestätigung und gewährleistet so Transparenz sowie die Gewissheit, dass die Matrixaktualisierungen und Schwellenwerte wie vorgesehen funktionieren.
Fazit: Sie verfügen nun über eine strukturierte Methode, um die korrelationsbasierte Risikosteuerung direkt in einen Multi-Paar-EA zu integrieren und so ein gleichmäßigeres und kontrollierteres Portfolioverhalten zu erzielen. Anstatt die sich verändernden Beziehungen zwischen den Märkten manuell zu überwachen – oder Korrelationsrisiken erst dann zu erkennen, wenn bereits Kursverluste eingetreten sind –, steuert das System strukturelle Risiken proaktiv in Echtzeit und reduziert damit eine der häufigsten Schwachstellen des Handels mit mehreren Symbolen im Live-Handel.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21460
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Larry Williams’ Marktgeheimnisse (Teil 15): Handel mit Hidden-Smash Day-Umkehrungen unter Berücksichtigung des Marktkontexts
Eine alternative Log-datei mit der Verwendung der HTML und CSS
MQL5-Handelswerkzeuge (Teil 24): Verbesserte Tiefenwahrnehmung mit 3D-Kurven, Pan-Modus und ViewCube-Navigation
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.